From 45fc1d6ec356232e51fe4a9cc1f01929e35e8014 Mon Sep 17 00:00:00 2001 From: Kevin Serrano Date: Wed, 9 Aug 2017 17:40:01 -0700 Subject: [PATCH 001/140] Readd loose label onto accounts. --- ui/app/app.js | 3 +++ ui/app/components/account-dropdowns.js | 20 +++++++++++++++++++- ui/app/components/dropdown.js | 2 +- ui/app/css/lib.css | 5 +++-- 4 files changed, 26 insertions(+), 4 deletions(-) diff --git a/ui/app/app.js b/ui/app/app.js index 4565bdd37..2186c85a2 100644 --- a/ui/app/app.js +++ b/ui/app/app.js @@ -42,6 +42,7 @@ function mapStateToProps (state) { identities, accounts, address, + keyrings, } = state.metamask const selected = address || Object.keys(accounts)[0] @@ -69,6 +70,7 @@ function mapStateToProps (state) { // state needed to get account dropdown temporarily rendering from app bar identities, selected, + keyrings, } } @@ -187,6 +189,7 @@ App.prototype.renderAppBar = function () { identities: this.props.identities, selected: this.props.selected, network: this.props.network, + keyrings: this.props.keyrings, }, []), // hamburger diff --git a/ui/app/components/account-dropdowns.js b/ui/app/components/account-dropdowns.js index b23600e9b..9a68b8f20 100644 --- a/ui/app/components/account-dropdowns.js +++ b/ui/app/components/account-dropdowns.js @@ -22,12 +22,19 @@ class AccountDropdowns extends Component { } renderAccounts () { - const { identities, selected } = this.props + const { identities, selected, keyrings } = this.props return Object.keys(identities).map((key, index) => { const identity = identities[key] const isSelected = identity.address === selected + const simpleAddress = identity.address.substring(2).toLowerCase() + + const keyring = keyrings.find((kr) => { + return kr.accounts.includes(simpleAddress) || + kr.accounts.includes(identity.address) + }) + return h( DropdownMenuItem, { @@ -51,6 +58,7 @@ class AccountDropdowns extends Component { }, }, ), + this.indicateIfLoose(keyring.type), h('span', { style: { marginLeft: '20px', fontSize: '24px' } }, identity.name || ''), h('span', { style: { marginLeft: '20px', fontSize: '24px' } }, isSelected ? h('.check', '✓') : null), ] @@ -58,6 +66,13 @@ class AccountDropdowns extends Component { }) } + indicateIfLoose (type) { + try { // Sometimes keyrings aren't loaded yet: + const isLoose = type !== 'HD Key Tree' + return isLoose ? h('.keyring-label', 'LOOSE') : null + } catch (e) { return } + } + renderAccountSelector () { const { actions } = this.props const { accountSelectorActive } = this.state @@ -136,6 +151,8 @@ class AccountDropdowns extends Component { ) } + + renderAccountOptions () { const { actions } = this.props const { optionsMenuActive } = this.state @@ -269,6 +286,7 @@ AccountDropdowns.defaultProps = { AccountDropdowns.propTypes = { identities: PropTypes.objectOf(PropTypes.object), selected: PropTypes.string, + keyrings: PropTypes.objectOf(PropTypes.object), } const mapDispatchToProps = (dispatch) => { diff --git a/ui/app/components/dropdown.js b/ui/app/components/dropdown.js index 34c7149ee..73710acc2 100644 --- a/ui/app/components/dropdown.js +++ b/ui/app/components/dropdown.js @@ -32,7 +32,7 @@ class Dropdown extends Component { 'style', ` li.dropdown-menu-item:hover { color:rgb(225, 225, 225); } - li.dropdown-menu-item { color: rgb(185, 185, 185); } + li.dropdown-menu-item { color: rgb(185, 185, 185); position: relative } ` ), ...children, diff --git a/ui/app/css/lib.css b/ui/app/css/lib.css index 81647d1c1..f3acbee76 100644 --- a/ui/app/css/lib.css +++ b/ui/app/css/lib.css @@ -215,12 +215,13 @@ hr.horizontal-line { z-index: 1; font-size: 11px; background: rgba(255,0,0,0.8); - bottom: -47px; color: white; + bottom: 0px; + left: -8px; border-radius: 10px; height: 20px; min-width: 20px; - position: relative; + position: absolute; display: flex; align-items: center; justify-content: center; From 399c51c04831381da0b9013018763b43618de614 Mon Sep 17 00:00:00 2001 From: DavidNinja Date: Mon, 14 Aug 2017 21:29:21 -0700 Subject: [PATCH 002/140] cancel all txs --- ui/app/actions.js | 8 ++++++++ ui/app/conf-tx.js | 12 ++++++++++++ 2 files changed, 20 insertions(+) diff --git a/ui/app/actions.js b/ui/app/actions.js index eafd04b4c..d5ee3cc31 100644 --- a/ui/app/actions.js +++ b/ui/app/actions.js @@ -105,6 +105,8 @@ var actions = { txError: txError, nextTx: nextTx, previousTx: previousTx, + cancelAllTx: cancelAllTx, + CANCEL_ALL_TX: 'CANCEL_ALL_TX', viewPendingTx: viewPendingTx, VIEW_PENDING_TX: 'VIEW_PENDING_TX', // app messages @@ -470,6 +472,12 @@ function cancelTx (txData) { } } +function cancelAllTx (txsData) { + return (dispatch) => { + txsData.forEach((txData) => dispatch(actions.cancelTx(txData))) + dispatch(actions.goHome()) + } +} // // initialize screen // diff --git a/ui/app/conf-tx.js b/ui/app/conf-tx.js index 34727ff78..d6ceb7ff5 100644 --- a/ui/app/conf-tx.js +++ b/ui/app/conf-tx.js @@ -89,6 +89,18 @@ ConfirmTxScreen.prototype.render = function () { }), ]), + h('h3', { + style: { + alignSelf: 'flex-end', + display: unconfTxList.length > 1 ? 'block' : 'none', + }, + }, [ + h('i.fa.fa-trash.fa-lg.cursor-pointer', { + title: 'Cancel All Pending Transactions', + onClick: () => props.dispatch(actions.cancelAllTx(unconfTxList)), + }), + ]), + warningIfExists(props.warning), currentTxView({ From 39424cdf457e8348b50dde2588ad173aa4787fca Mon Sep 17 00:00:00 2001 From: davidp94 Date: Mon, 14 Aug 2017 21:35:57 -0700 Subject: [PATCH 003/140] remove unused action type --- ui/app/actions.js | 1 - 1 file changed, 1 deletion(-) diff --git a/ui/app/actions.js b/ui/app/actions.js index d5ee3cc31..3b6b87b00 100644 --- a/ui/app/actions.js +++ b/ui/app/actions.js @@ -106,7 +106,6 @@ var actions = { nextTx: nextTx, previousTx: previousTx, cancelAllTx: cancelAllTx, - CANCEL_ALL_TX: 'CANCEL_ALL_TX', viewPendingTx: viewPendingTx, VIEW_PENDING_TX: 'VIEW_PENDING_TX', // app messages From f52ef5d49657d126a2575c91c83481209e8ef0ab Mon Sep 17 00:00:00 2001 From: davidp94 Date: Mon, 14 Aug 2017 21:51:39 -0700 Subject: [PATCH 004/140] fix goHome --- ui/app/actions.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/ui/app/actions.js b/ui/app/actions.js index 3b6b87b00..a692267cb 100644 --- a/ui/app/actions.js +++ b/ui/app/actions.js @@ -473,8 +473,12 @@ function cancelTx (txData) { function cancelAllTx (txsData) { return (dispatch) => { - txsData.forEach((txData) => dispatch(actions.cancelTx(txData))) - dispatch(actions.goHome()) + txsData.forEach((txData, i) => { + background.cancelTransaction(txData.id, () => { + dispatch(actions.completedTx(txData.id)) + i === txsData.length - 1 ? dispatch(actions.goHome()) : null + }) + }) } } // From cc56d0d2f61da576acc72b1e7f63df4015469267 Mon Sep 17 00:00:00 2001 From: kumavis Date: Thu, 24 Aug 2017 15:44:40 -0700 Subject: [PATCH 005/140] inpage - use json-rpc-engine for inpage-provider --- app/scripts/lib/inpage-provider.js | 46 +++++++----------------------- app/scripts/metamask-controller.js | 42 ++++++++++++++++++++------- package.json | 1 + 3 files changed, 43 insertions(+), 46 deletions(-) diff --git a/app/scripts/lib/inpage-provider.js b/app/scripts/lib/inpage-provider.js index fd032a673..de6e8b811 100644 --- a/app/scripts/lib/inpage-provider.js +++ b/app/scripts/lib/inpage-provider.js @@ -1,8 +1,8 @@ const pipe = require('pump') -const StreamProvider = require('web3-stream-provider') +const RpcEngine = require('json-rpc-engine') +const createStreamMiddleware = require('json-rpc-middleware-stream') const LocalStorageStore = require('obs-store') const ObjectMultiplex = require('./obj-multiplex') -const createRandomId = require('./random-id') module.exports = MetamaskInpageProvider @@ -30,38 +30,20 @@ function MetamaskInpageProvider (connectionStream) { multiStream.ignoreStream('phishing') // connect to async provider - const asyncProvider = self.asyncProvider = new StreamProvider() + const streamMiddleware = createStreamMiddleware() pipe( - asyncProvider, + streamMiddleware.stream, multiStream.createStream('provider'), - asyncProvider, + streamMiddleware.stream, (err) => logStreamDisconnectWarning('MetaMask RpcProvider', err) ) // start and stop polling to unblock first block lock - self.idMap = {} - // handle sendAsync requests via asyncProvider - self.sendAsync = function (payload, cb) { - // rewrite request ids - var request = eachJsonMessage(payload, (message) => { - var newId = createRandomId() - self.idMap[newId] = message.id - message.id = newId - return message - }) - // forward to asyncProvider - asyncProvider.sendAsync(request, function (err, res) { - if (err) return cb(err) - // transform messages to original ids - eachJsonMessage(res, (message) => { - var oldId = self.idMap[message.id] - delete self.idMap[message.id] - message.id = oldId - return message - }) - cb(null, res) - }) - } + // handle sendAsync requests via dapp-side rpc engine + const engine = new RpcEngine() + engine.push(streamMiddleware) + + self.sendAsync = engine.handle.bind(engine) } MetamaskInpageProvider.prototype.send = function (payload) { @@ -121,14 +103,6 @@ MetamaskInpageProvider.prototype.isMetaMask = true // util -function eachJsonMessage (payload, transformFn) { - if (Array.isArray(payload)) { - return payload.map(transformFn) - } else { - return transformFn(payload) - } -} - function logStreamDisconnectWarning (remoteLabel, err) { let warningMsg = `MetamaskInpageProvider - lost connection to ${remoteLabel}` if (err) warningMsg += '\n' + err.stack diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index a007d6fc5..e4b1b5975 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -6,7 +6,8 @@ const Dnode = require('dnode') const ObservableStore = require('obs-store') const EthStore = require('./lib/eth-store') const EthQuery = require('eth-query') -const streamIntoProvider = require('web3-stream-provider/handler') +const RpcEngine = require('json-rpc-engine') +const createEngineStream = require('json-rpc-middleware-stream/engineStream') const setupMultiplex = require('./lib/stream-utils.js').setupMultiplex const KeyringController = require('./keyring-controller') const NetworkController = require('./controllers/network') @@ -375,19 +376,40 @@ module.exports = class MetamaskController extends EventEmitter { } setupProviderConnection (outStream, originDomain) { - streamIntoProvider(outStream, this.provider, onRequest, onResponse) + const engine = new RpcEngine() + engine.push(originMiddleware) + engine.push(loggerMiddleware) + engine.push(createProviderMiddleware({ provider: this.provider })) + + // setup connection + const providerStream = createEngineStream({ engine }) + outStream.pipe(providerStream).pipe(outStream) + // append dapp origin domain to request - function onRequest (request) { - request.origin = originDomain + function originMiddleware (req, res, next, end) { + req.origin = originDomain + next() } // log rpc activity - function onResponse (err, request, response) { - if (err) return console.error(err) - if (response.error) { - console.error('Error in RPC response:\n', response) + function loggerMiddleware (req, res, next, end) { + next((cb) => { + if (res.error) { + console.error('Error in RPC response:\n', res) + } + if (req.isMetamaskInternal) return + log.info(`RPC (${originDomain}):`, req, '->', res) + cb() + }) + } + // forward requests to provider + function createProviderMiddleware({ provider }) { + return (req, res, next, end) => { + provider.sendAsync(req, (err, _res) => { + if (err) return end(err) + res.result = _res.result + end() + }) } - if (request.isMetamaskInternal) return - log.info(`RPC (${originDomain}):`, request, '->', response) } } diff --git a/package.json b/package.json index 9a09e6305..a0d241341 100644 --- a/package.json +++ b/package.json @@ -93,6 +93,7 @@ "iframe-stream": "^3.0.0", "inject-css": "^0.1.1", "jazzicon": "^1.2.0", + "json-rpc-middleware-stream": "^1.0.0", "loglevel": "^1.4.1", "metamask-logo": "^2.1.2", "mississippi": "^1.2.0", From 2117490d41a31eeb2d39383bfc5ae7751f445420 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Tue, 29 Aug 2017 11:36:53 -0700 Subject: [PATCH 006/140] Add test for trying to reproduce faq#67 Failed to reproduce this issue with the same inputs. --- test/unit/nonce-tracker-test.js | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/test/unit/nonce-tracker-test.js b/test/unit/nonce-tracker-test.js index 3312a3bd0..8970cf84d 100644 --- a/test/unit/nonce-tracker-test.js +++ b/test/unit/nonce-tracker-test.js @@ -162,6 +162,25 @@ describe('Nonce Tracker', function () { await nonceLock.releaseLock() }) }) + + describe('Faq issue 67', function () { + beforeEach(function () { + const txGen = new MockTxGen() + const confirmedTxs = txGen.generate({ status: 'confirmed' }, { count: 64 }) + const pendingTxs = txGen.generate({ + status: 'submitted', + }, { count: 10 }) + // 0x40 is 64 in hex: + nonceTracker = generateNonceTrackerWith(pendingTxs, [], '0x40') + }) + + it('should return nonce after network nonce', async function () { + this.timeout(15000) + const nonceLock = await nonceTracker.getNonceLock('0x7d3517b0d011698406d6e0aed8453f0be2697926') + assert.equal(nonceLock.nextNonce, '74', `nonce should be 74 got ${nonceLock.nextNonce}`) + await nonceLock.releaseLock() + }) + }) }) }) From 5e934994d1f1fdfd97da9ef1cbbce52e3583cc33 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Wed, 6 Sep 2017 13:33:30 -0700 Subject: [PATCH 007/140] Version 3.9.12 --- CHANGELOG.md | 3 +++ app/manifest.json | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index aa9e1ab6c..c738ac375 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,9 @@ ## Current Master +## 3.9.12 2017-9-6 + +- Fix bug that prevented Web3 1.0 compatibility - Make eth_sign deprecation warning less noisy - Add useful link to eth_sign deprecation warning. - Fix bug with network version serialization over synchronous RPC diff --git a/app/manifest.json b/app/manifest.json index 7ec32ea78..256737c89 100644 --- a/app/manifest.json +++ b/app/manifest.json @@ -1,7 +1,7 @@ { "name": "MetaMask", "short_name": "Metamask", - "version": "3.9.11", + "version": "3.9.12", "manifest_version": 2, "author": "https://metamask.io", "description": "Ethereum Browser Extension", From b5f48730cd2c024997d30fa272ab08ba7f686893 Mon Sep 17 00:00:00 2001 From: Kevin Serrano Date: Wed, 6 Sep 2017 13:59:41 -0700 Subject: [PATCH 008/140] Properly update keyring state on new account addition. --- app/scripts/keyring-controller.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/scripts/keyring-controller.js b/app/scripts/keyring-controller.js index 2edc8060e..fd57fac70 100644 --- a/app/scripts/keyring-controller.js +++ b/app/scripts/keyring-controller.js @@ -171,9 +171,9 @@ class KeyringController extends EventEmitter { return this.setupAccounts(checkedAccounts) }) .then(() => this.persistAllKeyrings()) + .then(() => this._updateMemStoreKeyrings()) .then(() => this.fullUpdate()) .then(() => { - this._updateMemStoreKeyrings() return keyring }) } @@ -208,6 +208,7 @@ class KeyringController extends EventEmitter { return selectedKeyring.addAccounts(1) .then(this.setupAccounts.bind(this)) .then(this.persistAllKeyrings.bind(this)) + .then(this._updateMemStoreKeyrings.bind(this)) .then(this.fullUpdate.bind(this)) } From 7ff5fd8691d9fb9339de8cbe1aa0dd27f7e824e7 Mon Sep 17 00:00:00 2001 From: Kevin Serrano Date: Wed, 6 Sep 2017 14:04:17 -0700 Subject: [PATCH 009/140] Changelog bump --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c738ac375..0c3aab200 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ # Changelog ## Current Master +- Readded loose keyring label back into the account list. ## 3.9.12 2017-9-6 From 714df393b406c515b0bb3abd4fcd8bde4ad53fde Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Wed, 6 Sep 2017 14:27:21 -0700 Subject: [PATCH 010/140] Add test template --- test/unit/pending-balance-test.js | 37 +++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 test/unit/pending-balance-test.js diff --git a/test/unit/pending-balance-test.js b/test/unit/pending-balance-test.js new file mode 100644 index 000000000..845d6d552 --- /dev/null +++ b/test/unit/pending-balance-test.js @@ -0,0 +1,37 @@ +const assert = require('assert') +const PendingBalanceCalculator = require('../../app/scripts/lib/pending-balance-calculator') +const MockTxGen = require('../lib/mock-tx-gen') +const BN = require('ethereumjs-util').BN +let providerResultStub = {} + +describe('PendingBalanceCalculator', function () { + let nonceTracker + + describe('if you have no pending txs and one ether', function () { + const ether = '0x' + (new BN(1e18)).toString(16) + + beforeEach(function () { + nonceTracker = generateNonceTrackerWith([], ether) + }) + + it('returns the network balance', function () { + const result = nonceTracker.getBalance() + assert.equal(result, ether, 'returns one ether') + }) + }) +}) + +function generateBalaneCalcWith (transactions, providerStub = '0x0') { + const getPendingTransactions = () => transactions + providerResultStub.result = providerStub + const provider = { + sendAsync: (_, cb) => { cb(undefined, providerResultStub) }, + _blockTracker: { + getCurrentBlock: () => '0x11b568', + }, + } + return new PendingBalanceCalculator({ + provider, + getPendingTransactions, + }) +} From f9a052deed8749a1c6dd544df561967a568749d9 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Wed, 6 Sep 2017 14:36:15 -0700 Subject: [PATCH 011/140] Add first passing balance calc test --- app/scripts/lib/pending-balance-calculator.js | 18 ++++++++++++++++++ test/unit/pending-balance-test.js | 15 ++++++++------- 2 files changed, 26 insertions(+), 7 deletions(-) create mode 100644 app/scripts/lib/pending-balance-calculator.js diff --git a/app/scripts/lib/pending-balance-calculator.js b/app/scripts/lib/pending-balance-calculator.js new file mode 100644 index 000000000..5b30354a2 --- /dev/null +++ b/app/scripts/lib/pending-balance-calculator.js @@ -0,0 +1,18 @@ +const BN = require('ethereumjs-util').BN +const EthQuery = require('ethjs-query') + +class PendingBalanceCalculator { + + constructor ({ getBalance, getPendingTransactions }) { + this.getPendingTransactions = getPendingTransactions + this.getBalance = getBalance + } + + async getBalance() { + const balance = await this.getBalance + return balance + } + +} + +module.exports = PendingBalanceCalculator diff --git a/test/unit/pending-balance-test.js b/test/unit/pending-balance-test.js index 845d6d552..dcf1926f0 100644 --- a/test/unit/pending-balance-test.js +++ b/test/unit/pending-balance-test.js @@ -8,21 +8,22 @@ describe('PendingBalanceCalculator', function () { let nonceTracker describe('if you have no pending txs and one ether', function () { - const ether = '0x' + (new BN(1e18)).toString(16) + const ether = '0x' + (new BN(String(1e18))).toString(16) beforeEach(function () { - nonceTracker = generateNonceTrackerWith([], ether) + nonceTracker = generateBalaneCalcWith([], ether) }) - it('returns the network balance', function () { - const result = nonceTracker.getBalance() - assert.equal(result, ether, 'returns one ether') + it('returns the network balance', async function () { + const result = await nonceTracker.getBalance() + assert.equal(result, ether, `gave ${result} needed ${ether}`) }) }) }) function generateBalaneCalcWith (transactions, providerStub = '0x0') { - const getPendingTransactions = () => transactions + const getPendingTransactions = () => Promise.resolve(transactions) + const getBalance = () => Promise.resolve(providerStub) providerResultStub.result = providerStub const provider = { sendAsync: (_, cb) => { cb(undefined, providerResultStub) }, @@ -31,7 +32,7 @@ function generateBalaneCalcWith (transactions, providerStub = '0x0') { }, } return new PendingBalanceCalculator({ - provider, + getBalance, getPendingTransactions, }) } From 74f7fc4613d136b57a4395d273ce4bf52d6685db Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Wed, 6 Sep 2017 14:37:46 -0700 Subject: [PATCH 012/140] Check balances in parallel --- app/scripts/lib/pending-balance-calculator.js | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/app/scripts/lib/pending-balance-calculator.js b/app/scripts/lib/pending-balance-calculator.js index 5b30354a2..4f6e03138 100644 --- a/app/scripts/lib/pending-balance-calculator.js +++ b/app/scripts/lib/pending-balance-calculator.js @@ -9,7 +9,14 @@ class PendingBalanceCalculator { } async getBalance() { - const balance = await this.getBalance + const results = await Promise.all([ + this.getBalance(), + this.getPendingTransactions(), + ]) + + const balance = results[0] + const pending = results[1] + return balance } From 440a42bbc38ed53b64dc017fd56bd3281355df33 Mon Sep 17 00:00:00 2001 From: kumavis Date: Thu, 7 Sep 2017 10:08:07 -0700 Subject: [PATCH 013/140] inpage - add idRemapMiddleware --- app/scripts/lib/inpage-provider.js | 4 +++- package.json | 1 + 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/app/scripts/lib/inpage-provider.js b/app/scripts/lib/inpage-provider.js index de6e8b811..c095846e1 100644 --- a/app/scripts/lib/inpage-provider.js +++ b/app/scripts/lib/inpage-provider.js @@ -1,5 +1,6 @@ const pipe = require('pump') const RpcEngine = require('json-rpc-engine') +const createIdRemapMiddleware = require('json-rpc-engine/src/idRemapMiddleware') const createStreamMiddleware = require('json-rpc-middleware-stream') const LocalStorageStore = require('obs-store') const ObjectMultiplex = require('./obj-multiplex') @@ -27,7 +28,7 @@ function MetamaskInpageProvider (connectionStream) { ) // ignore phishing warning message (handled elsewhere) - multiStream.ignoreStream('phishing') + multiStream.ignoreStream('phishing') // connect to async provider const streamMiddleware = createStreamMiddleware() @@ -41,6 +42,7 @@ function MetamaskInpageProvider (connectionStream) { // handle sendAsync requests via dapp-side rpc engine const engine = new RpcEngine() + engine.push(createIdRemapMiddleware()) engine.push(streamMiddleware) self.sendAsync = engine.handle.bind(engine) diff --git a/package.json b/package.json index a0d241341..7712707c6 100644 --- a/package.json +++ b/package.json @@ -93,6 +93,7 @@ "iframe-stream": "^3.0.0", "inject-css": "^0.1.1", "jazzicon": "^1.2.0", + "json-rpc-engine": "^3.1.0", "json-rpc-middleware-stream": "^1.0.0", "loglevel": "^1.4.1", "metamask-logo": "^2.1.2", From f0ab4ce52d4383a6b07646b829023ca5217e4e6c Mon Sep 17 00:00:00 2001 From: Kevin Serrano Date: Thu, 7 Sep 2017 11:51:04 -0700 Subject: [PATCH 014/140] Change expected type to array. --- ui/app/components/account-dropdowns.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/app/components/account-dropdowns.js b/ui/app/components/account-dropdowns.js index c23f61c98..a53f3399d 100644 --- a/ui/app/components/account-dropdowns.js +++ b/ui/app/components/account-dropdowns.js @@ -295,7 +295,7 @@ AccountDropdowns.defaultProps = { AccountDropdowns.propTypes = { identities: PropTypes.objectOf(PropTypes.object), selected: PropTypes.string, - keyrings: PropTypes.objectOf(PropTypes.object), + keyrings: PropTypes.array, } const mapDispatchToProps = (dispatch) => { From b6e8791bc2bc912d874edcc92fcf3c4ce5a9b72a Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Thu, 7 Sep 2017 11:59:15 -0700 Subject: [PATCH 015/140] test not passing --- app/scripts/lib/pending-balance-calculator.js | 18 +++++++++- test/unit/pending-balance-test.js | 35 +++++++++++++++++-- 2 files changed, 49 insertions(+), 4 deletions(-) diff --git a/app/scripts/lib/pending-balance-calculator.js b/app/scripts/lib/pending-balance-calculator.js index 4f6e03138..9df87e34b 100644 --- a/app/scripts/lib/pending-balance-calculator.js +++ b/app/scripts/lib/pending-balance-calculator.js @@ -1,5 +1,6 @@ const BN = require('ethereumjs-util').BN const EthQuery = require('ethjs-query') +const normalize = require('eth-sig-util').normalize class PendingBalanceCalculator { @@ -9,15 +10,30 @@ class PendingBalanceCalculator { } async getBalance() { + console.log('getting balance') const results = await Promise.all([ this.getBalance(), this.getPendingTransactions(), ]) + console.dir(results) const balance = results[0] const pending = results[1] - return balance + console.dir({ balance, pending }) + + const pendingValue = pending.reduce(function (total, tx) { + return total.sub(this.valueFor(tx)) + }, new BN(0)) + + const balanceBn = new BN(normalize(balance)) + + return `0x${ balanceBn.sub(pendingValue).toString(16) }` + } + + valueFor (tx) { + const value = new BN(normalize(tx.txParams.value)) + return value } } diff --git a/test/unit/pending-balance-test.js b/test/unit/pending-balance-test.js index dcf1926f0..9077e8f14 100644 --- a/test/unit/pending-balance-test.js +++ b/test/unit/pending-balance-test.js @@ -5,20 +5,49 @@ const BN = require('ethereumjs-util').BN let providerResultStub = {} describe('PendingBalanceCalculator', function () { - let nonceTracker + let balanceCalculator describe('if you have no pending txs and one ether', function () { const ether = '0x' + (new BN(String(1e18))).toString(16) beforeEach(function () { - nonceTracker = generateBalaneCalcWith([], ether) + balanceCalculator = generateBalaneCalcWith([], ether) }) it('returns the network balance', async function () { - const result = await nonceTracker.getBalance() + const result = await balanceCalculator.getBalance() assert.equal(result, ether, `gave ${result} needed ${ether}`) }) }) + + describe('if you have a one ether pending tx and one ether', function () { + const ether = '0x' + (new BN(String(1e18))).toString(16) + + beforeEach(function () { + const txGen = new MockTxGen() + pendingTxs = txGen.generate({ + status: 'submitted', + txParams: { + value: ether, + gasPrice: '0x0', + gas: '0x0', + } + }, { count: 1 }) + + balanceCalculator = generateBalaneCalcWith(pendingTxs, ether) + }) + + it('returns the network balance', async function () { + console.log('one') + console.dir(balanceCalculator) + const result = await balanceCalculator.getBalance() + console.log('two') + console.dir(result) + assert.equal(result, '0x0', `gave ${result} needed '0x0'`) + return true + }) + + }) }) function generateBalaneCalcWith (transactions, providerStub = '0x0') { From 69c7fe24b3f14a885bb2ff0bb936d1a35b521de3 Mon Sep 17 00:00:00 2001 From: Kevin Serrano Date: Thu, 7 Sep 2017 12:17:36 -0700 Subject: [PATCH 016/140] Place object property retrieval inside try-catch --- ui/app/components/account-dropdowns.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/ui/app/components/account-dropdowns.js b/ui/app/components/account-dropdowns.js index a53f3399d..b087a40d4 100644 --- a/ui/app/components/account-dropdowns.js +++ b/ui/app/components/account-dropdowns.js @@ -58,7 +58,7 @@ class AccountDropdowns extends Component { }, }, ), - this.indicateIfLoose(keyring.type), + this.indicateIfLoose(keyring), h('span', { style: { marginLeft: '20px', @@ -75,8 +75,9 @@ class AccountDropdowns extends Component { }) } - indicateIfLoose (type) { + indicateIfLoose (keyring) { try { // Sometimes keyrings aren't loaded yet: + const type = keyring.type const isLoose = type !== 'HD Key Tree' return isLoose ? h('.keyring-label', 'LOOSE') : null } catch (e) { return } From 40585744365c128d1f64c5bf93ee8cedc9e91dae Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Thu, 7 Sep 2017 12:30:25 -0700 Subject: [PATCH 017/140] Add basic test for valueFor --- app/scripts/lib/pending-balance-calculator.js | 13 +++++++--- test/unit/pending-balance-test.js | 25 ++++++++++++++++--- 2 files changed, 31 insertions(+), 7 deletions(-) diff --git a/app/scripts/lib/pending-balance-calculator.js b/app/scripts/lib/pending-balance-calculator.js index 9df87e34b..f2c9ce379 100644 --- a/app/scripts/lib/pending-balance-calculator.js +++ b/app/scripts/lib/pending-balance-calculator.js @@ -6,13 +6,13 @@ class PendingBalanceCalculator { constructor ({ getBalance, getPendingTransactions }) { this.getPendingTransactions = getPendingTransactions - this.getBalance = getBalance + this.getNetworkBalance = getBalance } async getBalance() { console.log('getting balance') const results = await Promise.all([ - this.getBalance(), + this.getNetworkBalance(), this.getPendingTransactions(), ]) console.dir(results) @@ -21,18 +21,23 @@ class PendingBalanceCalculator { const pending = results[1] console.dir({ balance, pending }) + console.dir(pending) const pendingValue = pending.reduce(function (total, tx) { - return total.sub(this.valueFor(tx)) + return total.add(this.valueFor(tx)) }, new BN(0)) const balanceBn = new BN(normalize(balance)) + console.log(`subtracting ${pendingValue.toString()} from ${balanceBn.toString()}`) return `0x${ balanceBn.sub(pendingValue).toString(16) }` } valueFor (tx) { - const value = new BN(normalize(tx.txParams.value)) + const txValue = tx.txParams.value + const normalized = normalize(txValue).substring(2) + console.log({ txValue, normalized }) + const value = new BN(normalize(txValue).substring(2), 16) return value } diff --git a/test/unit/pending-balance-test.js b/test/unit/pending-balance-test.js index 9077e8f14..7f20270cb 100644 --- a/test/unit/pending-balance-test.js +++ b/test/unit/pending-balance-test.js @@ -4,11 +4,31 @@ const MockTxGen = require('../lib/mock-tx-gen') const BN = require('ethereumjs-util').BN let providerResultStub = {} +const etherBn = new BN(String(1e18)) +const ether = '0x' + etherBn.toString(16) + describe('PendingBalanceCalculator', function () { let balanceCalculator + describe('#valueFor(tx)', function () { + it('returns a BN for a given tx value', function () { + const txGen = new MockTxGen() + pendingTxs = txGen.generate({ + status: 'submitted', + txParams: { + value: ether, + gasPrice: '0x0', + gas: '0x0', + } + }, { count: 1 }) + + const balanceCalculator = generateBalaneCalcWith([], '0x0') + const result = balanceCalculator.valueFor(pendingTxs[0]) + assert.equal(result.toString(), etherBn.toString(), 'computes one ether') + }) + }) + describe('if you have no pending txs and one ether', function () { - const ether = '0x' + (new BN(String(1e18))).toString(16) beforeEach(function () { balanceCalculator = generateBalaneCalcWith([], ether) @@ -21,8 +41,6 @@ describe('PendingBalanceCalculator', function () { }) describe('if you have a one ether pending tx and one ether', function () { - const ether = '0x' + (new BN(String(1e18))).toString(16) - beforeEach(function () { const txGen = new MockTxGen() pendingTxs = txGen.generate({ @@ -40,6 +58,7 @@ describe('PendingBalanceCalculator', function () { it('returns the network balance', async function () { console.log('one') console.dir(balanceCalculator) + console.dir(balanceCalculator.getBalance.toString()) const result = await balanceCalculator.getBalance() console.log('two') console.dir(result) From 7b92268428cc2de4374bc669c524bb61959801f1 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Thu, 7 Sep 2017 12:43:10 -0700 Subject: [PATCH 018/140] Fix valueFor test --- app/scripts/lib/pending-balance-calculator.js | 15 ++++++++------- test/unit/pending-balance-test.js | 9 +++++---- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/app/scripts/lib/pending-balance-calculator.js b/app/scripts/lib/pending-balance-calculator.js index f2c9ce379..e4ff1e050 100644 --- a/app/scripts/lib/pending-balance-calculator.js +++ b/app/scripts/lib/pending-balance-calculator.js @@ -15,32 +15,33 @@ class PendingBalanceCalculator { this.getNetworkBalance(), this.getPendingTransactions(), ]) - console.dir(results) const balance = results[0] const pending = results[1] - console.dir({ balance, pending }) console.dir(pending) - const pendingValue = pending.reduce(function (total, tx) { + const pendingValue = pending.reduce((total, tx) => { return total.add(this.valueFor(tx)) }, new BN(0)) - const balanceBn = new BN(normalize(balance)) - console.log(`subtracting ${pendingValue.toString()} from ${balanceBn.toString()}`) + console.log(`subtracting ${pendingValue.toString()} from ${balance.toString()}`) - return `0x${ balanceBn.sub(pendingValue).toString(16) }` + return `0x${ balance.sub(pendingValue).toString(16) }` } valueFor (tx) { const txValue = tx.txParams.value const normalized = normalize(txValue).substring(2) console.log({ txValue, normalized }) - const value = new BN(normalize(txValue).substring(2), 16) + const value = this.hexToBn(txValue) return value } + hexToBn (hex) { + return new BN(normalize(hex).substring(2), 16) + } + } module.exports = PendingBalanceCalculator diff --git a/test/unit/pending-balance-test.js b/test/unit/pending-balance-test.js index 7f20270cb..0937579e2 100644 --- a/test/unit/pending-balance-test.js +++ b/test/unit/pending-balance-test.js @@ -4,6 +4,7 @@ const MockTxGen = require('../lib/mock-tx-gen') const BN = require('ethereumjs-util').BN let providerResultStub = {} +const zeroBn = new BN(0) const etherBn = new BN(String(1e18)) const ether = '0x' + etherBn.toString(16) @@ -22,7 +23,7 @@ describe('PendingBalanceCalculator', function () { } }, { count: 1 }) - const balanceCalculator = generateBalaneCalcWith([], '0x0') + const balanceCalculator = generateBalanceCalcWith([], zeroBn) const result = balanceCalculator.valueFor(pendingTxs[0]) assert.equal(result.toString(), etherBn.toString(), 'computes one ether') }) @@ -31,7 +32,7 @@ describe('PendingBalanceCalculator', function () { describe('if you have no pending txs and one ether', function () { beforeEach(function () { - balanceCalculator = generateBalaneCalcWith([], ether) + balanceCalculator = generateBalanceCalcWith([], zeroBn) }) it('returns the network balance', async function () { @@ -52,7 +53,7 @@ describe('PendingBalanceCalculator', function () { } }, { count: 1 }) - balanceCalculator = generateBalaneCalcWith(pendingTxs, ether) + balanceCalculator = generateBalanceCalcWith(pendingTxs, etherBn) }) it('returns the network balance', async function () { @@ -69,7 +70,7 @@ describe('PendingBalanceCalculator', function () { }) }) -function generateBalaneCalcWith (transactions, providerStub = '0x0') { +function generateBalanceCalcWith (transactions, providerStub = zeroBn) { const getPendingTransactions = () => Promise.resolve(transactions) const getBalance = () => Promise.resolve(providerStub) providerResultStub.result = providerStub From 74c6de7d23c979c091028d8bd599f389e2090bc1 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Thu, 7 Sep 2017 12:45:00 -0700 Subject: [PATCH 019/140] Add constructor comment --- app/scripts/lib/pending-balance-calculator.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/app/scripts/lib/pending-balance-calculator.js b/app/scripts/lib/pending-balance-calculator.js index e4ff1e050..d5e2e4c17 100644 --- a/app/scripts/lib/pending-balance-calculator.js +++ b/app/scripts/lib/pending-balance-calculator.js @@ -4,6 +4,11 @@ const normalize = require('eth-sig-util').normalize class PendingBalanceCalculator { + // Must be initialized with two functions: + // getBalance => Returns a promise of a BN of the current balance in Wei + // getPendingTransactions => Returns an array of TxMeta Objects, + // which have txParams properties, which include value, gasPrice, and gas, + // all in a base=16 hex format. constructor ({ getBalance, getPendingTransactions }) { this.getPendingTransactions = getPendingTransactions this.getNetworkBalance = getBalance From a95a3c7e4f4d1331394a7bf92a77678fe8087c04 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Thu, 7 Sep 2017 12:47:27 -0700 Subject: [PATCH 020/140] Fix balance calc test --- app/scripts/lib/pending-balance-calculator.js | 2 ++ test/unit/pending-balance-test.js | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/app/scripts/lib/pending-balance-calculator.js b/app/scripts/lib/pending-balance-calculator.js index d5e2e4c17..4e1189a65 100644 --- a/app/scripts/lib/pending-balance-calculator.js +++ b/app/scripts/lib/pending-balance-calculator.js @@ -25,6 +25,8 @@ class PendingBalanceCalculator { const pending = results[1] console.dir(pending) + console.dir(balance.toString()) + console.trace('but why') const pendingValue = pending.reduce((total, tx) => { return total.add(this.valueFor(tx)) diff --git a/test/unit/pending-balance-test.js b/test/unit/pending-balance-test.js index 0937579e2..a9b0f7b66 100644 --- a/test/unit/pending-balance-test.js +++ b/test/unit/pending-balance-test.js @@ -32,7 +32,7 @@ describe('PendingBalanceCalculator', function () { describe('if you have no pending txs and one ether', function () { beforeEach(function () { - balanceCalculator = generateBalanceCalcWith([], zeroBn) + balanceCalculator = generateBalanceCalcWith([], etherBn) }) it('returns the network balance', async function () { From c616581001a7413a289b108b347005d53fb14732 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Thu, 7 Sep 2017 12:47:52 -0700 Subject: [PATCH 021/140] Remove logs --- app/scripts/lib/pending-balance-calculator.js | 8 -------- test/unit/pending-balance-test.js | 5 ----- 2 files changed, 13 deletions(-) diff --git a/app/scripts/lib/pending-balance-calculator.js b/app/scripts/lib/pending-balance-calculator.js index 4e1189a65..8564f0134 100644 --- a/app/scripts/lib/pending-balance-calculator.js +++ b/app/scripts/lib/pending-balance-calculator.js @@ -15,7 +15,6 @@ class PendingBalanceCalculator { } async getBalance() { - console.log('getting balance') const results = await Promise.all([ this.getNetworkBalance(), this.getPendingTransactions(), @@ -24,23 +23,16 @@ class PendingBalanceCalculator { const balance = results[0] const pending = results[1] - console.dir(pending) - console.dir(balance.toString()) - console.trace('but why') - const pendingValue = pending.reduce((total, tx) => { return total.add(this.valueFor(tx)) }, new BN(0)) - console.log(`subtracting ${pendingValue.toString()} from ${balance.toString()}`) - return `0x${ balance.sub(pendingValue).toString(16) }` } valueFor (tx) { const txValue = tx.txParams.value const normalized = normalize(txValue).substring(2) - console.log({ txValue, normalized }) const value = this.hexToBn(txValue) return value } diff --git a/test/unit/pending-balance-test.js b/test/unit/pending-balance-test.js index a9b0f7b66..e1d5f9303 100644 --- a/test/unit/pending-balance-test.js +++ b/test/unit/pending-balance-test.js @@ -57,12 +57,7 @@ describe('PendingBalanceCalculator', function () { }) it('returns the network balance', async function () { - console.log('one') - console.dir(balanceCalculator) - console.dir(balanceCalculator.getBalance.toString()) const result = await balanceCalculator.getBalance() - console.log('two') - console.dir(result) assert.equal(result, '0x0', `gave ${result} needed '0x0'`) return true }) From 532a4040de191d455053aa11432dcb9b407c9bf4 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Thu, 7 Sep 2017 12:52:25 -0700 Subject: [PATCH 022/140] Add test for computing gas price --- test/unit/pending-balance-test.js | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/test/unit/pending-balance-test.js b/test/unit/pending-balance-test.js index e1d5f9303..3029a849c 100644 --- a/test/unit/pending-balance-test.js +++ b/test/unit/pending-balance-test.js @@ -27,6 +27,22 @@ describe('PendingBalanceCalculator', function () { const result = balanceCalculator.valueFor(pendingTxs[0]) assert.equal(result.toString(), etherBn.toString(), 'computes one ether') }) + + it('calculates gas costs as well', function () { + const txGen = new MockTxGen() + pendingTxs = txGen.generate({ + status: 'submitted', + txParams: { + value: '0x0', + gasPrice: '0x2', + gas: '0x3', + } + }, { count: 1 }) + + const balanceCalculator = generateBalanceCalcWith([], zeroBn) + const result = balanceCalculator.valueFor(pendingTxs[0]) + assert.equal(result.toString(), '6', 'computes one ether') + }) }) describe('if you have no pending txs and one ether', function () { From fadc0617df016ad1fa3d1c92e220a8e9ede6379d Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Thu, 7 Sep 2017 12:52:49 -0700 Subject: [PATCH 023/140] Make tx calculations account for gas prices --- app/scripts/lib/pending-balance-calculator.js | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/app/scripts/lib/pending-balance-calculator.js b/app/scripts/lib/pending-balance-calculator.js index 8564f0134..29f1fd63a 100644 --- a/app/scripts/lib/pending-balance-calculator.js +++ b/app/scripts/lib/pending-balance-calculator.js @@ -32,9 +32,15 @@ class PendingBalanceCalculator { valueFor (tx) { const txValue = tx.txParams.value - const normalized = normalize(txValue).substring(2) const value = this.hexToBn(txValue) - return value + const gasPrice = this.hexToBn(tx.txParams.gasPrice) + + const gas = tx.txParams.gas + const gasLimit = tx.txParams.gasLimit + const gasLimitBn = this.hexToBn(gas || gasLimit) + + const gasCost = gasPrice.mul(gasLimitBn) + return value.add(gasCost) } hexToBn (hex) { From 65c00e9fbbb4496db4adee13c227e47ba85837c6 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Thu, 7 Sep 2017 12:53:30 -0700 Subject: [PATCH 024/140] Improve test name --- test/unit/pending-balance-test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/unit/pending-balance-test.js b/test/unit/pending-balance-test.js index 3029a849c..17be0306b 100644 --- a/test/unit/pending-balance-test.js +++ b/test/unit/pending-balance-test.js @@ -72,7 +72,7 @@ describe('PendingBalanceCalculator', function () { balanceCalculator = generateBalanceCalcWith(pendingTxs, etherBn) }) - it('returns the network balance', async function () { + it('returns the subtracted result', async function () { const result = await balanceCalculator.getBalance() assert.equal(result, '0x0', `gave ${result} needed '0x0'`) return true From d4d7c6d89eeddbe865e32b0f3636cc9de2a17cc1 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Thu, 7 Sep 2017 12:54:28 -0700 Subject: [PATCH 025/140] Linted --- app/scripts/lib/pending-balance-calculator.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/app/scripts/lib/pending-balance-calculator.js b/app/scripts/lib/pending-balance-calculator.js index 29f1fd63a..474ed3261 100644 --- a/app/scripts/lib/pending-balance-calculator.js +++ b/app/scripts/lib/pending-balance-calculator.js @@ -1,5 +1,4 @@ const BN = require('ethereumjs-util').BN -const EthQuery = require('ethjs-query') const normalize = require('eth-sig-util').normalize class PendingBalanceCalculator { @@ -27,7 +26,7 @@ class PendingBalanceCalculator { return total.add(this.valueFor(tx)) }, new BN(0)) - return `0x${ balance.sub(pendingValue).toString(16) }` + return `0x${balance.sub(pendingValue).toString(16)}` } valueFor (tx) { From 3a87a30912e8c5e212f7d0cc5e01ed1f873bfe33 Mon Sep 17 00:00:00 2001 From: Kevin Serrano Date: Thu, 7 Sep 2017 13:55:59 -0700 Subject: [PATCH 026/140] Fold submit and buy ether into one button slot --- ui/app/components/pending-tx.js | 24 +++++++++--------------- 1 file changed, 9 insertions(+), 15 deletions(-) diff --git a/ui/app/components/pending-tx.js b/ui/app/components/pending-tx.js index 5324ccd64..65f64adfe 100644 --- a/ui/app/components/pending-tx.js +++ b/ui/app/components/pending-tx.js @@ -66,6 +66,7 @@ PendingTx.prototype.render = function () { const balanceBn = hexToBn(balance) const insufficientBalance = balanceBn.lt(maxCost) + const buyDisabled = insufficientBalance || !this.state.valid || !isValidAddress || this.state.submitting this.inputs = [] @@ -297,14 +298,6 @@ PendingTx.prototype.render = function () { margin: '14px 25px', }, }, [ - - - insufficientBalance ? - h('button.btn-green', { - onClick: props.buyEth, - }, 'Buy Ether') - : null, - h('button', { onClick: (event) => { this.resetGasFields() @@ -312,13 +305,14 @@ PendingTx.prototype.render = function () { }, }, 'Reset'), - // Accept Button - h('input.confirm.btn-green', { - type: 'submit', - value: 'SUBMIT', - style: { marginLeft: '10px' }, - disabled: insufficientBalance || !this.state.valid || !isValidAddress || this.state.submitting, - }), + // Accept Button or Buy Button + insufficientBalance ? h('button.btn-green', { onClick: props.buyEth }, 'Buy Ether') : + h('input.confirm.btn-green', { + type: 'submit', + value: 'SUBMIT', + style: { marginLeft: '10px' }, + disabled: buyDisabled, + }), h('button.cancel.btn-red', { onClick: props.cancelTransaction, From 41e9904d9b6a6a9b828c02c7fe34b8eefb631374 Mon Sep 17 00:00:00 2001 From: Kevin Serrano Date: Thu, 7 Sep 2017 15:03:25 -0700 Subject: [PATCH 027/140] Convert icon to button. --- ui/app/components/pending-tx.js | 11 +++++++++++ ui/app/conf-tx.js | 19 +++++++------------ 2 files changed, 18 insertions(+), 12 deletions(-) diff --git a/ui/app/components/pending-tx.js b/ui/app/components/pending-tx.js index 65f64adfe..4e7eeaecf 100644 --- a/ui/app/components/pending-tx.js +++ b/ui/app/components/pending-tx.js @@ -318,6 +318,17 @@ PendingTx.prototype.render = function () { onClick: props.cancelTransaction, }, 'Reject'), ]), + h('.flex-row.flex-space-around.conf-buttons', { + style: { + display: 'flex', + justifyContent: 'flex-end', + margin: '14px 25px', + }, + }, [ + h('button.cancel.btn-red', { + onClick: props.cancelAllTransactions, + }, 'Reject All'), + ]), ]), ]) ) diff --git a/ui/app/conf-tx.js b/ui/app/conf-tx.js index d6ceb7ff5..8d819181f 100644 --- a/ui/app/conf-tx.js +++ b/ui/app/conf-tx.js @@ -89,18 +89,6 @@ ConfirmTxScreen.prototype.render = function () { }), ]), - h('h3', { - style: { - alignSelf: 'flex-end', - display: unconfTxList.length > 1 ? 'block' : 'none', - }, - }, [ - h('i.fa.fa-trash.fa-lg.cursor-pointer', { - title: 'Cancel All Pending Transactions', - onClick: () => props.dispatch(actions.cancelAllTx(unconfTxList)), - }), - ]), - warningIfExists(props.warning), currentTxView({ @@ -117,6 +105,7 @@ ConfirmTxScreen.prototype.render = function () { buyEth: this.buyEth.bind(this, txParams.from || props.selectedAddress), sendTransaction: this.sendTransaction.bind(this), cancelTransaction: this.cancelTransaction.bind(this, txData), + cancelAllTransactions: this.cancelAllTransactions.bind(this, unconfTxList), signMessage: this.signMessage.bind(this, txData), signPersonalMessage: this.signPersonalMessage.bind(this, txData), cancelMessage: this.cancelMessage.bind(this, txData), @@ -163,6 +152,12 @@ ConfirmTxScreen.prototype.cancelTransaction = function (txData, event) { this.props.dispatch(actions.cancelTx(txData)) } +ConfirmTxScreen.prototype.cancelAllTransactions = function (unconfTxList, event) { + this.stopPropagation(event) + event.preventDefault() + this.props.dispatch(actions.cancelAllTx(unconfTxList)) +} + ConfirmTxScreen.prototype.signMessage = function (msgData, event) { log.info('conf-tx.js: signing message') var params = msgData.msgParams From 55b07b2b4a3c501b77f619685a458b5f42b68f24 Mon Sep 17 00:00:00 2001 From: Kevin Serrano Date: Thu, 7 Sep 2017 15:06:57 -0700 Subject: [PATCH 028/140] Changelog and attribution. --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0c3aab200..0a2edb478 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ## Current Master - Readded loose keyring label back into the account list. +- Added button to reject all transactions (thanks [davidp94](https://github.com/davidp94)!) ## 3.9.12 2017-9-6 From 64717726c40e46dc7decffbaf3db6da0f4b0dc11 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Thu, 7 Sep 2017 15:27:30 -0700 Subject: [PATCH 029/140] Reword forgot password to restore seed phrase This always confuses people, I just finally did it. --- ui/app/unlock.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/app/unlock.js b/ui/app/unlock.js index 9bacd5124..4180791c4 100644 --- a/ui/app/unlock.js +++ b/ui/app/unlock.js @@ -80,7 +80,7 @@ UnlockScreen.prototype.render = function () { color: 'rgb(247, 134, 28)', textDecoration: 'underline', }, - }, 'I forgot my password.'), + }, 'Restore from seed phrase'), ]), ]) ) From 8d7f78c1a543cbbeb9d6599be346fc99db866340 Mon Sep 17 00:00:00 2001 From: Kevin Serrano Date: Thu, 7 Sep 2017 16:18:32 -0700 Subject: [PATCH 030/140] Add caret to indicate network is dropdown. --- ui/app/components/network.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/ui/app/components/network.js b/ui/app/components/network.js index 698a0bbb9..c7a28b363 100644 --- a/ui/app/components/network.js +++ b/ui/app/components/network.js @@ -74,6 +74,7 @@ Network.prototype.render = function () { color: '#039396', }}, 'Ethereum Main Net'), + h('i.fa.fa-caret-down.fa-lg'), ]) case 'ropsten-test-network': return h('.network-indicator', [ @@ -83,6 +84,7 @@ Network.prototype.render = function () { color: '#ff6666', }}, 'Ropsten Test Net'), + h('i.fa.fa-caret-down.fa-lg'), ]) case 'kovan-test-network': return h('.network-indicator', [ @@ -92,6 +94,7 @@ Network.prototype.render = function () { color: '#690496', }}, 'Kovan Test Net'), + h('i.fa.fa-caret-down.fa-lg'), ]) case 'rinkeby-test-network': return h('.network-indicator', [ @@ -101,6 +104,7 @@ Network.prototype.render = function () { color: '#e7a218', }}, 'Rinkeby Test Net'), + h('i.fa.fa-caret-down.fa-lg'), ]) default: return h('.network-indicator', [ @@ -116,6 +120,7 @@ Network.prototype.render = function () { color: '#AEAEAE', }}, 'Private Network'), + h('i.fa.fa-caret-down.fa-lg'), ]) } })(), From 54fb8e8cef9b2692d1ad1bac9efd5cbe6eadf823 Mon Sep 17 00:00:00 2001 From: Kevin Serrano Date: Thu, 7 Sep 2017 16:26:31 -0700 Subject: [PATCH 031/140] Add cursor on hover on loading. Change some wording. --- ui/app/components/network.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ui/app/components/network.js b/ui/app/components/network.js index c7a28b363..0dbe37cdd 100644 --- a/ui/app/components/network.js +++ b/ui/app/components/network.js @@ -22,7 +22,7 @@ Network.prototype.render = function () { let iconName, hoverText if (networkNumber === 'loading') { - return h('span', { + return h('span.pointer', { style: { display: 'flex', alignItems: 'center', @@ -37,7 +37,7 @@ Network.prototype.render = function () { }, src: 'images/loading.svg', }), - h('i.fa.fa-sort-desc'), + h('i.fa.fa-caret-down'), ]) } else if (providerName === 'mainnet') { hoverText = 'Main Ethereum Network' @@ -73,7 +73,7 @@ Network.prototype.render = function () { style: { color: '#039396', }}, - 'Ethereum Main Net'), + 'Main Network'), h('i.fa.fa-caret-down.fa-lg'), ]) case 'ropsten-test-network': From 6560a2c3af66940e9fbfcc9943655ca5c7282121 Mon Sep 17 00:00:00 2001 From: Kevin Serrano Date: Thu, 7 Sep 2017 17:55:34 -0700 Subject: [PATCH 032/140] Add front-end validation for own adddresses as a token contract address. Add information on token contract addresses. --- ui/app/add-token.js | 30 ++++++++++++++++++++++++------ 1 file changed, 24 insertions(+), 6 deletions(-) diff --git a/ui/app/add-token.js b/ui/app/add-token.js index 15ef7a852..81894247b 100644 --- a/ui/app/add-token.js +++ b/ui/app/add-token.js @@ -3,6 +3,8 @@ const Component = require('react').Component const h = require('react-hyperscript') const connect = require('react-redux').connect const actions = require('./actions') +const Tooltip = require('./components/tooltip.js') + const ethUtil = require('ethereumjs-util') const abi = require('human-standard-token-abi') @@ -15,6 +17,7 @@ module.exports = connect(mapStateToProps)(AddTokenScreen) function mapStateToProps (state) { return { + identities: state.metamask.identities, } } @@ -64,15 +67,24 @@ AddTokenScreen.prototype.render = function () { }, [ h('div', [ - h('span', { - style: { fontWeight: 'bold', paddingRight: '10px'}, - }, 'Token Address'), + h(Tooltip, { + position: 'top', + title: 'The contract of the actual token contract. Click for more info.', + }, [ + h('a', { + style: { fontWeight: 'bold', paddingRight: '10px'}, + href: 'https://consensyssupport.happyfox.com/staff/kb/article/24-what-is-a-token-contract-address', + }, [ + h('span', 'Token Contract Address '), + h('i.fa.fa-question-circle'), + ]), + ]), ]), h('section.flex-row.flex-center', [ h('input#token-address', { name: 'address', - placeholder: 'Token Address', + placeholder: 'Token Contract Address', onChange: this.tokenAddressDidChange.bind(this), style: { width: 'inherit', @@ -171,7 +183,9 @@ AddTokenScreen.prototype.tokenAddressDidChange = function (event) { AddTokenScreen.prototype.validateInputs = function () { let msg = '' const state = this.state + const identitiesList = Object.keys(this.props.identities) const { address, symbol, decimals } = state + const standardAddress = ethUtil.addHexPrefix(address).toLowerCase() const validAddress = ethUtil.isValidAddress(address) if (!validAddress) { @@ -189,7 +203,12 @@ AddTokenScreen.prototype.validateInputs = function () { msg += 'Symbol must be between 0 and 10 characters.' } - const isValid = validAddress && validDecimals + const ownAddress = identitiesList.includes(standardAddress) + if (ownAddress) { + msg = 'Personal address detected. Input the token contact address.' + } + + const isValid = validAddress && validDecimals && !ownAddress if (!isValid) { this.setState({ @@ -216,4 +235,3 @@ AddTokenScreen.prototype.attemptToAutoFillTokenParams = async function (address) this.setState({ symbol: symbol[0], decimals: decimals[0].toString() }) } } - From 2200a1142ff609ec467edf8b78d70be79622588d Mon Sep 17 00:00:00 2001 From: Kevin Serrano Date: Thu, 7 Sep 2017 17:55:49 -0700 Subject: [PATCH 033/140] Bump changelog. --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0c3aab200..a63a8604a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Current Master - Readded loose keyring label back into the account list. +- Add info on token contract addresses. +- Add validation preventing users from inputting their own addresses as token tracking addresses. ## 3.9.12 2017-9-6 From 54a5a117d55db826ac20059f70d748cd90516270 Mon Sep 17 00:00:00 2001 From: Kevin Serrano Date: Thu, 7 Sep 2017 18:00:45 -0700 Subject: [PATCH 034/140] Fix link to open new tab. --- ui/app/add-token.js | 1 + 1 file changed, 1 insertion(+) diff --git a/ui/app/add-token.js b/ui/app/add-token.js index 81894247b..177adc572 100644 --- a/ui/app/add-token.js +++ b/ui/app/add-token.js @@ -74,6 +74,7 @@ AddTokenScreen.prototype.render = function () { h('a', { style: { fontWeight: 'bold', paddingRight: '10px'}, href: 'https://consensyssupport.happyfox.com/staff/kb/article/24-what-is-a-token-contract-address', + target: '_blank', }, [ h('span', 'Token Contract Address '), h('i.fa.fa-question-circle'), From 0e6c11a3b5e7677d8d37da4d41bf19b09f85cd88 Mon Sep 17 00:00:00 2001 From: Kevin Serrano Date: Thu, 7 Sep 2017 18:01:39 -0700 Subject: [PATCH 035/140] Fix typo. --- ui/app/add-token.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/app/add-token.js b/ui/app/add-token.js index 177adc572..18adc7eb5 100644 --- a/ui/app/add-token.js +++ b/ui/app/add-token.js @@ -206,7 +206,7 @@ AddTokenScreen.prototype.validateInputs = function () { const ownAddress = identitiesList.includes(standardAddress) if (ownAddress) { - msg = 'Personal address detected. Input the token contact address.' + msg = 'Personal address detected. Input the token contract address.' } const isValid = validAddress && validDecimals && !ownAddress From b7e24dcda7d67d0504ceb463ab1c1c1d77450bca Mon Sep 17 00:00:00 2001 From: Kevin Serrano Date: Thu, 7 Sep 2017 18:17:43 -0700 Subject: [PATCH 036/140] Remove cryptonator from chrome permissions. --- app/manifest.json | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/app/manifest.json b/app/manifest.json index 256737c89..96d495178 100644 --- a/app/manifest.json +++ b/app/manifest.json @@ -57,9 +57,8 @@ "permissions": [ "storage", "clipboardWrite", - "http://localhost:8545/", - "https://api.cryptonator.com/" - ], + "http://localhost:8545/" + ], "web_accessible_resources": [ "scripts/inpage.js" ], From a7f0255085a1358e8ee314228f5d215dc47ea958 Mon Sep 17 00:00:00 2001 From: Kevin Serrano Date: Thu, 7 Sep 2017 18:18:14 -0700 Subject: [PATCH 037/140] Add to changelog. --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0c3aab200..60e0e5674 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ## Current Master - Readded loose keyring label back into the account list. +- Remove cryptonator from chrome permissions. ## 3.9.12 2017-9-6 From 57e4805c621155cd86169064f4aaba34b73644c6 Mon Sep 17 00:00:00 2001 From: kumavis Date: Thu, 7 Sep 2017 21:17:49 -0700 Subject: [PATCH 038/140] streams - use pump and published obj-multiplex --- app/scripts/lib/obj-multiplex.js | 48 ------------------------------ app/scripts/lib/port-stream.js | 16 ++-------- app/scripts/lib/stream-utils.js | 23 +++++++------- app/scripts/metamask-controller.js | 34 ++++++++++++++++----- package.json | 4 ++- 5 files changed, 44 insertions(+), 81 deletions(-) delete mode 100644 app/scripts/lib/obj-multiplex.js diff --git a/app/scripts/lib/obj-multiplex.js b/app/scripts/lib/obj-multiplex.js deleted file mode 100644 index 0034febe0..000000000 --- a/app/scripts/lib/obj-multiplex.js +++ /dev/null @@ -1,48 +0,0 @@ -const through = require('through2') - -module.exports = ObjectMultiplex - -function ObjectMultiplex (opts) { - opts = opts || {} - // create multiplexer - const mx = through.obj(function (chunk, enc, cb) { - const name = chunk.name - const data = chunk.data - if (!name) { - console.warn(`ObjectMultiplex - Malformed chunk without name "${chunk}"`) - return cb() - } - const substream = mx.streams[name] - if (!substream) { - console.warn(`ObjectMultiplex - orphaned data for stream "${name}"`) - } else { - if (substream.push) substream.push(data) - } - return cb() - }) - mx.streams = {} - // create substreams - mx.createStream = function (name) { - const substream = mx.streams[name] = through.obj(function (chunk, enc, cb) { - mx.push({ - name: name, - data: chunk, - }) - return cb() - }) - mx.on('end', function () { - return substream.emit('end') - }) - if (opts.error) { - mx.on('error', function () { - return substream.emit('error') - }) - } - return substream - } - // ignore streams (dont display orphaned data warning) - mx.ignoreStream = function (name) { - mx.streams[name] = true - } - return mx -} diff --git a/app/scripts/lib/port-stream.js b/app/scripts/lib/port-stream.js index 607a9c9ed..648d88087 100644 --- a/app/scripts/lib/port-stream.js +++ b/app/scripts/lib/port-stream.js @@ -1,5 +1,6 @@ const Duplex = require('readable-stream').Duplex const inherits = require('util').inherits +const noop = function(){} module.exports = PortDuplexStream @@ -20,20 +21,14 @@ PortDuplexStream.prototype._onMessage = function (msg) { if (Buffer.isBuffer(msg)) { delete msg._isBuffer var data = new Buffer(msg) - // console.log('PortDuplexStream - saw message as buffer', data) this.push(data) } else { - // console.log('PortDuplexStream - saw message', msg) this.push(msg) } } PortDuplexStream.prototype._onDisconnect = function () { - try { - this.push(null) - } catch (err) { - this.emit('error', err) - } + this.destroy() } // stream plumbing @@ -45,19 +40,12 @@ PortDuplexStream.prototype._write = function (msg, encoding, cb) { if (Buffer.isBuffer(msg)) { var data = msg.toJSON() data._isBuffer = true - // console.log('PortDuplexStream - sent message as buffer', data) this._port.postMessage(data) } else { - // console.log('PortDuplexStream - sent message', msg) this._port.postMessage(msg) } } catch (err) { - // console.error(err) return cb(new Error('PortDuplexStream - disconnected')) } cb() } - -// util - -function noop () {} diff --git a/app/scripts/lib/stream-utils.js b/app/scripts/lib/stream-utils.js index ba79990cc..89e2a359e 100644 --- a/app/scripts/lib/stream-utils.js +++ b/app/scripts/lib/stream-utils.js @@ -1,6 +1,7 @@ const Through = require('through2') const endOfStream = require('end-of-stream') -const ObjectMultiplex = require('./obj-multiplex') +const ObjectMultiplex = require('obj-multiplex') +const pump = require('pump') module.exports = { jsonParseStream: jsonParseStream, @@ -23,14 +24,14 @@ function jsonStringifyStream () { } function setupMultiplex (connectionStream) { - var mx = ObjectMultiplex() - connectionStream.pipe(mx).pipe(connectionStream) - endOfStream(mx, function (err) { - if (err) console.error(err) - }) - endOfStream(connectionStream, function (err) { - if (err) console.error(err) - mx.destroy() - }) - return mx + const mux = new ObjectMultiplex() + pump( + connectionStream, + mux, + connectionStream, + (err) => { + if (err) console.error(err) + } + ) + return mux } diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index e4b1b5975..1a6732338 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -1,7 +1,7 @@ const EventEmitter = require('events') const extend = require('xtend') const promiseToCallback = require('promise-to-callback') -const pipe = require('pump') +const pump = require('pump') const Dnode = require('dnode') const ObservableStore = require('obs-store') const EthStore = require('./lib/eth-store') @@ -367,7 +367,14 @@ module.exports = class MetamaskController extends EventEmitter { setupControllerConnection (outStream) { const api = this.getApi() const dnode = Dnode(api) - outStream.pipe(dnode).pipe(outStream) + pump( + outStream, + dnode, + outStream, + (err) => { + if (err) console.error(err) + } + ) dnode.on('remote', (remote) => { // push updates to popup const sendUpdate = remote.sendUpdate.bind(remote) @@ -376,20 +383,29 @@ module.exports = class MetamaskController extends EventEmitter { } setupProviderConnection (outStream, originDomain) { + // setup json rpc engine stack const engine = new RpcEngine() engine.push(originMiddleware) engine.push(loggerMiddleware) engine.push(createProviderMiddleware({ provider: this.provider })) - + // setup connection const providerStream = createEngineStream({ engine }) - outStream.pipe(providerStream).pipe(outStream) - + pump( + outStream, + providerStream, + outStream, + (err) => { + if (err) console.error(err) + } + ) + // append dapp origin domain to request function originMiddleware (req, res, next, end) { req.origin = originDomain next() } + // log rpc activity function loggerMiddleware (req, res, next, end) { next((cb) => { @@ -401,6 +417,7 @@ module.exports = class MetamaskController extends EventEmitter { cb() }) } + // forward requests to provider function createProviderMiddleware({ provider }) { return (req, res, next, end) => { @@ -414,9 +431,12 @@ module.exports = class MetamaskController extends EventEmitter { } setupPublicConfig (outStream) { - pipe( + pump( this.publicConfigStore, - outStream + outStream, + (err) => { + if (err) console.error(err) + } ) } diff --git a/package.json b/package.json index 7712707c6..295d33409 100644 --- a/package.json +++ b/package.json @@ -69,6 +69,7 @@ "eth-bin-to-ops": "^1.0.1", "eth-contract-metadata": "^1.1.4", "eth-hd-keyring": "^1.1.1", + "eth-json-rpc-filters": "^1.0.1", "eth-phishing-detect": "^1.1.4", "eth-query": "^2.1.2", "eth-sig-util": "^1.2.2", @@ -101,6 +102,7 @@ "mkdirp": "^0.5.1", "multiplex": "^6.7.0", "number-to-bn": "^1.7.0", + "obj-multiplex": "^1.0.0", "obs-store": "^2.3.1", "once": "^1.3.3", "ping-pong-stream": "^1.0.0", @@ -121,7 +123,7 @@ "react-select": "^1.0.0-rc.2", "react-simple-file-input": "^1.0.0", "react-tooltip-component": "^0.3.0", - "readable-stream": "^2.1.2", + "readable-stream": "^2.3.3", "redux": "^3.0.5", "redux-logger": "^3.0.6", "redux-thunk": "^2.2.0", From 0e8e655fdb9b65df151f23ede74807025226ab66 Mon Sep 17 00:00:00 2001 From: kumavis Date: Thu, 7 Sep 2017 21:19:24 -0700 Subject: [PATCH 039/140] inpage - distinguish pump vs pipe --- app/scripts/lib/inpage-provider.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/scripts/lib/inpage-provider.js b/app/scripts/lib/inpage-provider.js index c095846e1..eb24dfcab 100644 --- a/app/scripts/lib/inpage-provider.js +++ b/app/scripts/lib/inpage-provider.js @@ -1,4 +1,4 @@ -const pipe = require('pump') +const pump = require('pump') const RpcEngine = require('json-rpc-engine') const createIdRemapMiddleware = require('json-rpc-engine/src/idRemapMiddleware') const createStreamMiddleware = require('json-rpc-middleware-stream') @@ -12,7 +12,7 @@ function MetamaskInpageProvider (connectionStream) { // setup connectionStream multiplexing var multiStream = self.multiStream = ObjectMultiplex() - pipe( + pump( connectionStream, multiStream, connectionStream, @@ -21,7 +21,7 @@ function MetamaskInpageProvider (connectionStream) { // subscribe to metamask public config (one-way) self.publicConfigStore = new LocalStorageStore({ storageKey: 'MetaMask-Config' }) - pipe( + pump( multiStream.createStream('publicConfig'), self.publicConfigStore, (err) => logStreamDisconnectWarning('MetaMask PublicConfigStore', err) @@ -32,7 +32,7 @@ function MetamaskInpageProvider (connectionStream) { // connect to async provider const streamMiddleware = createStreamMiddleware() - pipe( + pump( streamMiddleware.stream, multiStream.createStream('provider'), streamMiddleware.stream, From 9d4c02e57f2b147759d979d8a6c051aa008cdff0 Mon Sep 17 00:00:00 2001 From: kumavis Date: Thu, 7 Sep 2017 21:26:25 -0700 Subject: [PATCH 040/140] metamask - add jsonrpc filter middleware on per-connection engine --- app/scripts/metamask-controller.js | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index 1a6732338..735fc4af0 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -8,6 +8,7 @@ const EthStore = require('./lib/eth-store') const EthQuery = require('eth-query') const RpcEngine = require('json-rpc-engine') const createEngineStream = require('json-rpc-middleware-stream/engineStream') +const createFilterMiddleware = require('eth-json-rpc-filters') const setupMultiplex = require('./lib/stream-utils.js').setupMultiplex const KeyringController = require('./keyring-controller') const NetworkController = require('./controllers/network') @@ -78,12 +79,13 @@ module.exports = class MetamaskController extends EventEmitter { // rpc provider this.provider = this.initializeProvider() + this.blockTracker = this.provider // eth data query tools this.ethQuery = new EthQuery(this.provider) this.ethStore = new EthStore({ provider: this.provider, - blockTracker: this.provider, + blockTracker: this.blockTracker, }) // key mgmt @@ -110,7 +112,7 @@ module.exports = class MetamaskController extends EventEmitter { getNetwork: this.networkController.getNetworkState.bind(this), signTransaction: this.keyringController.signTransaction.bind(this.keyringController), provider: this.provider, - blockTracker: this.provider, + blockTracker: this.blockTracker, ethQuery: this.ethQuery, ethStore: this.ethStore, }) @@ -387,6 +389,10 @@ module.exports = class MetamaskController extends EventEmitter { const engine = new RpcEngine() engine.push(originMiddleware) engine.push(loggerMiddleware) + engine.push(createFilterMiddleware({ + provider: this.provider, + blockTracker: this.blockTracker, + })) engine.push(createProviderMiddleware({ provider: this.provider })) // setup connection From f5d0a0b07ac835810bc6faef88384c2872846414 Mon Sep 17 00:00:00 2001 From: kumavis Date: Thu, 7 Sep 2017 22:25:08 -0700 Subject: [PATCH 041/140] deps - bump jsonrpc filters for log filter formate fix --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 295d33409..3fede273c 100644 --- a/package.json +++ b/package.json @@ -69,7 +69,7 @@ "eth-bin-to-ops": "^1.0.1", "eth-contract-metadata": "^1.1.4", "eth-hd-keyring": "^1.1.1", - "eth-json-rpc-filters": "^1.0.1", + "eth-json-rpc-filters": "^1.0.2", "eth-phishing-detect": "^1.1.4", "eth-query": "^2.1.2", "eth-sig-util": "^1.2.2", From 70401626e2544f254ddb06a21b5d3de7fdd7fb82 Mon Sep 17 00:00:00 2001 From: kumavis Date: Thu, 7 Sep 2017 22:35:38 -0700 Subject: [PATCH 042/140] lint - remove dead code --- app/scripts/lib/stream-utils.js | 1 - 1 file changed, 1 deletion(-) diff --git a/app/scripts/lib/stream-utils.js b/app/scripts/lib/stream-utils.js index 89e2a359e..8bb0b4f3c 100644 --- a/app/scripts/lib/stream-utils.js +++ b/app/scripts/lib/stream-utils.js @@ -1,5 +1,4 @@ const Through = require('through2') -const endOfStream = require('end-of-stream') const ObjectMultiplex = require('obj-multiplex') const pump = require('pump') From ef3bf810bf14da6651ef849e481eb0253be3c8d1 Mon Sep 17 00:00:00 2001 From: kumavis Date: Thu, 7 Sep 2017 22:47:08 -0700 Subject: [PATCH 043/140] inpage - use obj-multiplex module --- app/scripts/lib/inpage-provider.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/app/scripts/lib/inpage-provider.js b/app/scripts/lib/inpage-provider.js index db46e4f17..b2515bfb8 100644 --- a/app/scripts/lib/inpage-provider.js +++ b/app/scripts/lib/inpage-provider.js @@ -3,7 +3,7 @@ const RpcEngine = require('json-rpc-engine') const createIdRemapMiddleware = require('json-rpc-engine/src/idRemapMiddleware') const createStreamMiddleware = require('json-rpc-middleware-stream') const LocalStorageStore = require('obs-store') -const ObjectMultiplex = require('./obj-multiplex') +const ObjectMultiplex = require('obj-multiplex') module.exports = MetamaskInpageProvider @@ -11,10 +11,10 @@ function MetamaskInpageProvider (connectionStream) { const self = this // setup connectionStream multiplexing - var multiStream = self.multiStream = ObjectMultiplex() + const mux = self.mux = new ObjectMultiplex() pump( connectionStream, - multiStream, + mux, connectionStream, (err) => logStreamDisconnectWarning('MetaMask', err) ) @@ -22,19 +22,19 @@ function MetamaskInpageProvider (connectionStream) { // subscribe to metamask public config (one-way) self.publicConfigStore = new LocalStorageStore({ storageKey: 'MetaMask-Config' }) pump( - multiStream.createStream('publicConfig'), + mux.createStream('publicConfig'), self.publicConfigStore, (err) => logStreamDisconnectWarning('MetaMask PublicConfigStore', err) ) // ignore phishing warning message (handled elsewhere) - multiStream.ignoreStream('phishing') + mux.ignoreStream('phishing') // connect to async provider const streamMiddleware = createStreamMiddleware() pump( streamMiddleware.stream, - multiStream.createStream('provider'), + mux.createStream('provider'), streamMiddleware.stream, (err) => logStreamDisconnectWarning('MetaMask RpcProvider', err) ) From 16957fa8ae90f940221f065e5aa7494efefc23da Mon Sep 17 00:00:00 2001 From: kumavis Date: Thu, 7 Sep 2017 22:49:53 -0700 Subject: [PATCH 044/140] ci - dont need global install of test utils --- circle.yml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/circle.yml b/circle.yml index 2ea60bb9d..e81e1bcaa 100644 --- a/circle.yml +++ b/circle.yml @@ -1,10 +1,6 @@ machine: node: version: 8.1.4 -dependencies: - pre: - - "npm i -g testem" - - "npm i -g mocha" test: override: - "npm run ci" \ No newline at end of file From d03b0547bb63c4d60fef9ba0d8ebbc88e3aa7b1e Mon Sep 17 00:00:00 2001 From: kumavis Date: Fri, 8 Sep 2017 11:52:00 -0700 Subject: [PATCH 045/140] inpage provider - define sendAsync on the prototype --- app/scripts/lib/inpage-provider.js | 52 ++++++++++++++++-------------- 1 file changed, 27 insertions(+), 25 deletions(-) diff --git a/app/scripts/lib/inpage-provider.js b/app/scripts/lib/inpage-provider.js index c63af06dc..13888dc67 100644 --- a/app/scripts/lib/inpage-provider.js +++ b/app/scripts/lib/inpage-provider.js @@ -40,31 +40,37 @@ function MetamaskInpageProvider (connectionStream) { // start and stop polling to unblock first block lock self.idMap = {} - // handle sendAsync requests via asyncProvider - self.sendAsync = function (payload, cb) { - // rewrite request ids - var request = eachJsonMessage(payload, (_message) => { - const message = Object.assign({}, _message) - const newId = createRandomId() - self.idMap[newId] = message.id - message.id = newId +} + +// handle sendAsync requests via asyncProvider +// also remap ids inbound and outbound +MetamaskInpageProvider.prototype.sendAsync = function (payload, cb) { + const self = this + + // rewrite request ids + const request = eachJsonMessage(payload, (_message) => { + const message = Object.assign({}, _message) + const newId = createRandomId() + self.idMap[newId] = message.id + message.id = newId + return message + }) + + // forward to asyncProvider + self.asyncProvider.sendAsync(request, (err, _res) => { + if (err) return cb(err) + // transform messages to original ids + const res = eachJsonMessage(_res, (message) => { + const oldId = self.idMap[message.id] + delete self.idMap[message.id] + message.id = oldId return message }) - // forward to asyncProvider - asyncProvider.sendAsync(request, function (err, res) { - if (err) return cb(err) - // transform messages to original ids - eachJsonMessage(res, (message) => { - var oldId = self.idMap[message.id] - delete self.idMap[message.id] - message.id = oldId - return message - }) - cb(null, res) - }) - } + cb(null, res) + }) } + MetamaskInpageProvider.prototype.send = function (payload) { const self = this @@ -110,10 +116,6 @@ MetamaskInpageProvider.prototype.send = function (payload) { } } -MetamaskInpageProvider.prototype.sendAsync = function () { - throw new Error('MetamaskInpageProvider - sendAsync not overwritten') -} - MetamaskInpageProvider.prototype.isConnected = function () { return true } From 585a32534d9415eec9f91b19732e121a4a4c3a79 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Fri, 8 Sep 2017 12:17:58 -0700 Subject: [PATCH 046/140] Version 3.9.13 --- CHANGELOG.md | 4 ++++ app/manifest.json | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c738ac375..9d528d253 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ ## Current Master +## 3.9.13 2017-9-8 + +- Changed the way we initialize the inpage provider to fix a bug affecting some developers. + ## 3.9.12 2017-9-6 - Fix bug that prevented Web3 1.0 compatibility diff --git a/app/manifest.json b/app/manifest.json index 256737c89..51e82d6e3 100644 --- a/app/manifest.json +++ b/app/manifest.json @@ -1,7 +1,7 @@ { "name": "MetaMask", "short_name": "Metamask", - "version": "3.9.12", + "version": "3.9.13", "manifest_version": 2, "author": "https://metamask.io", "description": "Ethereum Browser Extension", From 06889377c89099cad411f4b142c8803484a225f7 Mon Sep 17 00:00:00 2001 From: Kevin Serrano Date: Mon, 11 Sep 2017 14:00:35 -0700 Subject: [PATCH 047/140] Remove markdown from changelog. --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 50de1a465..c2bcd20f2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,10 +2,10 @@ ## Current Master - Readded loose keyring label back into the account list. -- Added button to reject all transactions (thanks [davidp94](https://github.com/davidp94)!) - Remove cryptonator from chrome permissions. - Add info on token contract addresses. - Add validation preventing users from inputting their own addresses as token tracking addresses. +- Added button to reject all transactions (thanks to davidp94! https://github.com/davidp94) ## 3.9.13 2017-9-8 From 27ba7f6ed3b0c55f6f5d7fa7e3829bdc06e0df9f Mon Sep 17 00:00:00 2001 From: Kevin Serrano Date: Mon, 11 Sep 2017 14:12:35 -0700 Subject: [PATCH 048/140] Show reject all only when tx count is > 1 --- ui/app/components/pending-tx.js | 5 +++-- ui/app/conf-tx.js | 3 +++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/ui/app/components/pending-tx.js b/ui/app/components/pending-tx.js index 4e7eeaecf..3e53d47f9 100644 --- a/ui/app/components/pending-tx.js +++ b/ui/app/components/pending-tx.js @@ -67,6 +67,7 @@ PendingTx.prototype.render = function () { const balanceBn = hexToBn(balance) const insufficientBalance = balanceBn.lt(maxCost) const buyDisabled = insufficientBalance || !this.state.valid || !isValidAddress || this.state.submitting + const showRejectAll = props.unconfTxListLength > 1 this.inputs = [] @@ -318,7 +319,7 @@ PendingTx.prototype.render = function () { onClick: props.cancelTransaction, }, 'Reject'), ]), - h('.flex-row.flex-space-around.conf-buttons', { + showRejectAll ? h('.flex-row.flex-space-around.conf-buttons', { style: { display: 'flex', justifyContent: 'flex-end', @@ -328,7 +329,7 @@ PendingTx.prototype.render = function () { h('button.cancel.btn-red', { onClick: props.cancelAllTransactions, }, 'Reject All'), - ]), + ]) : null, ]), ]) ) diff --git a/ui/app/conf-tx.js b/ui/app/conf-tx.js index 8d819181f..1ee4166f7 100644 --- a/ui/app/conf-tx.js +++ b/ui/app/conf-tx.js @@ -52,6 +52,8 @@ ConfirmTxScreen.prototype.render = function () { log.info(`rendering a combined ${unconfTxList.length} unconf msg & txs`) if (unconfTxList.length === 0) return h(Loading, { isLoading: true }) + const unconfTxListLength = unconfTxList.length + return ( h('.flex-column.flex-grow', [ @@ -101,6 +103,7 @@ ConfirmTxScreen.prototype.render = function () { conversionRate, currentCurrency, blockGasLimit, + unconfTxListLength, // Actions buyEth: this.buyEth.bind(this, txParams.from || props.selectedAddress), sendTransaction: this.sendTransaction.bind(this), From 8545453a9d58d7a54c10beef52e38107ed937117 Mon Sep 17 00:00:00 2001 From: kumavis Date: Mon, 11 Sep 2017 14:30:30 -0700 Subject: [PATCH 049/140] contentscript - fix obj-multiplex instantiation and use pump for streams --- app/scripts/contentscript.js | 45 ++++++++++++++++++++++++++---------- 1 file changed, 33 insertions(+), 12 deletions(-) diff --git a/app/scripts/contentscript.js b/app/scripts/contentscript.js index acacf5d4c..90a0f1f22 100644 --- a/app/scripts/contentscript.js +++ b/app/scripts/contentscript.js @@ -1,11 +1,12 @@ +const fs = require('fs') +const path = require('path') +const pump = require('pump') const LocalMessageDuplexStream = require('post-message-stream') const PongStream = require('ping-pong-stream/pong') -const PortStream = require('./lib/port-stream.js') -const ObjectMultiplex = require('./lib/obj-multiplex') +const ObjectMultiplex = require('obj-multiplex') const extension = require('extensionizer') +const PortStream = require('./lib/port-stream.js') -const fs = require('fs') -const path = require('path') const inpageText = fs.readFileSync(path.join(__dirname, 'inpage.js')).toString() // Eventually this streaming injection could be replaced with: @@ -50,22 +51,42 @@ function setupStreams () { pageStream.pipe(pluginStream).pipe(pageStream) // setup local multistream channels - const mx = ObjectMultiplex() - mx.on('error', console.error) - mx.pipe(pageStream).pipe(mx) - mx.pipe(pluginStream).pipe(mx) + const mux = new ObjectMultiplex() + pump( + mux, + pageStream, + mux, + (err) => logStreamDisconnectWarning('MetaMask Inpage', err) + ) + pump( + mux, + pluginStream, + mux, + (err) => logStreamDisconnectWarning('MetaMask Background', err) + ) // connect ping stream const pongStream = new PongStream({ objectMode: true }) - pongStream.pipe(mx.createStream('pingpong')).pipe(pongStream) + pump( + mux, + pongStream, + mux, + (err) => logStreamDisconnectWarning('MetaMask PingPongStream', err) + ) // connect phishing warning stream - const phishingStream = mx.createStream('phishing') + const phishingStream = mux.createStream('phishing') phishingStream.once('data', redirectToPhishingWarning) // ignore unused channels (handled by background, inpage) - mx.ignoreStream('provider') - mx.ignoreStream('publicConfig') + mux.ignoreStream('provider') + mux.ignoreStream('publicConfig') +} + +function logStreamDisconnectWarning (remoteLabel, err) { + let warningMsg = `MetamaskContentscript - lost connection to ${remoteLabel}` + if (err) warningMsg += '\n' + err.stack + console.warn(warningMsg) } function shouldInjectWeb3 () { From 6c318f238d6b6739c302c90ec86ed604a85a8f37 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Mon, 11 Sep 2017 14:37:53 -0700 Subject: [PATCH 050/140] Version 3.10.0 --- CHANGELOG.md | 3 +++ app/manifest.json | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c2bcd20f2..0d41742be 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,9 @@ # Changelog ## Current Master + +## 3.10.0 2017-9-11 + - Readded loose keyring label back into the account list. - Remove cryptonator from chrome permissions. - Add info on token contract addresses. diff --git a/app/manifest.json b/app/manifest.json index f597bec7f..bd25c1f6f 100644 --- a/app/manifest.json +++ b/app/manifest.json @@ -1,7 +1,7 @@ { "name": "MetaMask", "short_name": "Metamask", - "version": "3.9.13", + "version": "3.10.0", "manifest_version": 2, "author": "https://metamask.io", "description": "Ethereum Browser Extension", From a6d49a49eaaeee73d8b417188823393adf65794e Mon Sep 17 00:00:00 2001 From: kumavis Date: Mon, 11 Sep 2017 15:48:50 -0700 Subject: [PATCH 051/140] test - integration - use async fn for error catching --- test/integration/lib/first-time.js | 181 ++++++++++++++--------------- 1 file changed, 87 insertions(+), 94 deletions(-) diff --git a/test/integration/lib/first-time.js b/test/integration/lib/first-time.js index 0e4b802da..cc56b3704 100644 --- a/test/integration/lib/first-time.js +++ b/test/integration/lib/first-time.js @@ -2,125 +2,118 @@ const PASSWORD = 'password123' QUnit.module('first time usage') -QUnit.test('render init screen', function (assert) { - var done = assert.async() - let app - - wait().then(function() { - app = $('iframe').contents().find('#app-content .mock-app-root') - - const recurseNotices = function () { - let button = app.find('button') - if (button.html() === 'Accept') { - let termsPage = app.find('.markdown')[0] - termsPage.scrollTop = termsPage.scrollHeight - return wait().then(() => { - button.click() - return wait() - }).then(() => { - return recurseNotices() - }) - } else { - return wait() - } +QUnit.test('render init screen', (assert) => { + const done = assert.async() + runFirstTimeUsageTest(assert).then(done).catch((err) => { + assert.notOk(err, 'Should not error') + done() + }) +}) + +async function runFirstTimeUsageTest(assert, done) { + await wait() + + const app = $('iframe').contents().find('#app-content .mock-app-root') + + const recurseNotices = async () => { + const button = app.find('button') + if (button.html() === 'Accept') { + const termsPage = app.find('.markdown')[0] + termsPage.scrollTop = termsPage.scrollHeight + await wait() + button.click() + await wait() + await recurseNotices() + } else { + await wait() } - return recurseNotices() - }).then(function() { - // Scroll through terms - var title = app.find('h1').text() - assert.equal(title, 'MetaMask', 'title screen') + } - // enter password - var pwBox = app.find('#password-box')[0] - var confBox = app.find('#password-box-confirm')[0] - pwBox.value = PASSWORD - confBox.value = PASSWORD + await recurseNotices() - return wait() - }).then(function() { + // Scroll through terms + const title = app.find('h1').text() + assert.equal(title, 'MetaMask', 'title screen') - // create vault - var createButton = app.find('button.primary')[0] - createButton.click() + // enter password + const pwBox = app.find('#password-box')[0] + const confBox = app.find('#password-box-confirm')[0] + pwBox.value = PASSWORD + confBox.value = PASSWORD - return wait(1500) - }).then(function() { + await wait() - var created = app.find('h3')[0] - assert.equal(created.textContent, 'Vault Created', 'Vault created screen') + // create vault + const createButton = app.find('button.primary')[0] + createButton.click() - // Agree button - var button = app.find('button')[0] - assert.ok(button, 'button present') - button.click() + await wait(1500) - return wait(1000) - }).then(function() { + const created = app.find('h3')[0] + assert.equal(created.textContent, 'Vault Created', 'Vault created screen') - var detail = app.find('.account-detail-section')[0] - assert.ok(detail, 'Account detail section loaded.') + // Agree button + const button = app.find('button')[0] + assert.ok(button, 'button present') + button.click() - var sandwich = app.find('.sandwich-expando')[0] - sandwich.click() + await wait(1000) - return wait() - }).then(function() { + const detail = app.find('.account-detail-section')[0] + assert.ok(detail, 'Account detail section loaded.') - var sandwich = app.find('.menu-droppo')[0] - var children = sandwich.children - var lock = children[children.length - 2] - assert.ok(lock, 'Lock menu item found') - lock.click() + const sandwich = app.find('.sandwich-expando')[0] + sandwich.click() - return wait(1000) - }).then(function() { + await wait() - var pwBox = app.find('#password-box')[0] - pwBox.value = PASSWORD + const menu = app.find('.menu-droppo')[0] + const children = menu.children + const lock = children[children.length - 2] + assert.ok(lock, 'Lock menu item found') + lock.click() - var createButton = app.find('button.primary')[0] - createButton.click() + await wait(1000) - return wait(1000) - }).then(function() { + const pwBox2 = app.find('#password-box')[0] + pwBox2.value = PASSWORD - var detail = app.find('.account-detail-section')[0] - assert.ok(detail, 'Account detail section loaded again.') + const createButton2 = app.find('button.primary')[0] + createButton2.click() - return wait() - }).then(function (){ + await wait(1000) - var qrButton = app.find('.fa.fa-ellipsis-h')[0] // open account settings dropdown - qrButton.click() + const detail2 = app.find('.account-detail-section')[0] + assert.ok(detail2, 'Account detail section loaded again.') - return wait(1000) - }).then(function (){ + await wait() - var qrButton = app.find('.dropdown-menu-item')[1] // qr code item - qrButton.click() + // open account settings dropdown + const qrButton = app.find('.fa.fa-ellipsis-h')[0] + qrButton.click() - return wait(1000) - }).then(function (){ + await wait(1000) - var qrHeader = app.find('.qr-header')[0] - var qrContainer = app.find('#qr-container')[0] - assert.equal(qrHeader.textContent, 'Account 1', 'Should show account label.') - assert.ok(qrContainer, 'QR Container found') + // qr code item + const qrButton2 = app.find('.dropdown-menu-item')[1] + qrButton2.click() - return wait() - }).then(function (){ + await wait(1000) - var networkMenu = app.find('.network-indicator')[0] - networkMenu.click() + const qrHeader = app.find('.qr-header')[0] + const qrContainer = app.find('#qr-container')[0] + assert.equal(qrHeader.textContent, 'Account 1', 'Should show account label.') + assert.ok(qrContainer, 'QR Container found') - return wait() - }).then(function (){ + await wait() - var networkMenu = app.find('.network-indicator')[0] - var children = networkMenu.children - children.length[3] - assert.ok(children, 'All network options present') + const networkMenu = app.find('.network-indicator')[0] + networkMenu.click() - done() - }) -}) + await wait() + + const networkMenu2 = app.find('.network-indicator')[0] + const children2 = networkMenu2.children + children2.length[3] + assert.ok(children2, 'All network options present') +} \ No newline at end of file From 3a7d4a5d4e94d9a4c2556b161c5bad7dc00837a4 Mon Sep 17 00:00:00 2001 From: Kevin Serrano Date: Mon, 11 Sep 2017 16:13:28 -0700 Subject: [PATCH 052/140] Basic private key download file functionality. --- ui/app/components/account-export.js | 39 ++++++++++++++++++++++------- 1 file changed, 30 insertions(+), 9 deletions(-) diff --git a/ui/app/components/account-export.js b/ui/app/components/account-export.js index 330f73805..5bbdfca39 100644 --- a/ui/app/components/account-export.js +++ b/ui/app/components/account-export.js @@ -20,20 +20,21 @@ function mapStateToProps (state) { } ExportAccountView.prototype.render = function () { - var state = this.props - var accountDetail = state.accountDetail + const state = this.props + const accountDetail = state.accountDetail + const nickname = state.identities[state.address].name if (!accountDetail) return h('div') - var accountExport = accountDetail.accountExport + const accountExport = accountDetail.accountExport - var notExporting = accountExport === 'none' - var exportRequested = accountExport === 'requested' - var accountExported = accountExport === 'completed' + const notExporting = accountExport === 'none' + const exportRequested = accountExport === 'requested' + const accountExported = accountExport === 'completed' if (notExporting) return h('div') if (exportRequested) { - var warning = `Export private keys at your own risk.` + const warning = `Export private keys at your own risk.` return ( h('div', { style: { @@ -89,6 +90,8 @@ ExportAccountView.prototype.render = function () { } if (accountExported) { + const plainKey = ethUtil.stripHexPrefix(accountDetail.privateKey) + return h('div.privateKey', { style: { margin: '0 20px', @@ -105,10 +108,13 @@ ExportAccountView.prototype.render = function () { onClick: function (event) { copyToClipboard(ethUtil.stripHexPrefix(accountDetail.privateKey)) }, - }, ethUtil.stripHexPrefix(accountDetail.privateKey)), + }, plainKey), h('button', { onClick: () => this.props.dispatch(actions.backToAccountDetail(this.props.address)), }, 'Done'), + h('button', { + onClick: () => this.exportAsFile(`MetaMask ${nickname} Private Key`, plainKey), + }, 'Save as File'), ]) } } @@ -117,6 +123,21 @@ ExportAccountView.prototype.onExportKeyPress = function (event) { if (event.key !== 'Enter') return event.preventDefault() - var input = document.getElementById('exportAccount').value + const input = document.getElementById('exportAccount').value this.props.dispatch(actions.exportAccount(input, this.props.address)) } + +ExportAccountView.prototype.exportAsFile = function (filename, data) { + // source: https://stackoverflow.com/a/33542499 by Ludovic Feltz + const blob = new Blob([data], {type: 'text/csv'}) + if (window.navigator.msSaveOrOpenBlob) { + window.navigator.msSaveBlob(blob, filename) + } else { + const elem = window.document.createElement('a') + elem.href = window.URL.createObjectURL(blob) + elem.download = filename + document.body.appendChild(elem) + elem.click() + document.body.removeChild(elem) + } +} From fa1ec5dcd16ba09d31720a42d4015dedde91148c Mon Sep 17 00:00:00 2001 From: Kevin Serrano Date: Mon, 11 Sep 2017 16:22:20 -0700 Subject: [PATCH 053/140] Move function as util function. --- ui/app/components/account-export.js | 18 ++---------------- ui/app/util.js | 16 ++++++++++++++++ 2 files changed, 18 insertions(+), 16 deletions(-) diff --git a/ui/app/components/account-export.js b/ui/app/components/account-export.js index 5bbdfca39..d438c9ca5 100644 --- a/ui/app/components/account-export.js +++ b/ui/app/components/account-export.js @@ -1,6 +1,7 @@ const Component = require('react').Component const h = require('react-hyperscript') const inherits = require('util').inherits +const exportAsFile = require('../util').exportAsFile const copyToClipboard = require('copy-to-clipboard') const actions = require('../actions') const ethUtil = require('ethereumjs-util') @@ -113,7 +114,7 @@ ExportAccountView.prototype.render = function () { onClick: () => this.props.dispatch(actions.backToAccountDetail(this.props.address)), }, 'Done'), h('button', { - onClick: () => this.exportAsFile(`MetaMask ${nickname} Private Key`, plainKey), + onClick: () => exportAsFile(`MetaMask ${nickname} Private Key`, plainKey), }, 'Save as File'), ]) } @@ -126,18 +127,3 @@ ExportAccountView.prototype.onExportKeyPress = function (event) { const input = document.getElementById('exportAccount').value this.props.dispatch(actions.exportAccount(input, this.props.address)) } - -ExportAccountView.prototype.exportAsFile = function (filename, data) { - // source: https://stackoverflow.com/a/33542499 by Ludovic Feltz - const blob = new Blob([data], {type: 'text/csv'}) - if (window.navigator.msSaveOrOpenBlob) { - window.navigator.msSaveBlob(blob, filename) - } else { - const elem = window.document.createElement('a') - elem.href = window.URL.createObjectURL(blob) - elem.download = filename - document.body.appendChild(elem) - elem.click() - document.body.removeChild(elem) - } -} diff --git a/ui/app/util.js b/ui/app/util.js index ac3f42c6b..1368ebf11 100644 --- a/ui/app/util.js +++ b/ui/app/util.js @@ -36,6 +36,7 @@ module.exports = { valueTable: valueTable, bnTable: bnTable, isHex: isHex, + exportAsFile: exportAsFile, } function valuesFor (obj) { @@ -215,3 +216,18 @@ function readableDate (ms) { function isHex (str) { return Boolean(str.match(/^(0x)?[0-9a-fA-F]+$/)) } + +function exportAsFile (filename, data) { + // source: https://stackoverflow.com/a/33542499 by Ludovic Feltz + const blob = new Blob([data], {type: 'text/csv'}) + if (window.navigator.msSaveOrOpenBlob) { + window.navigator.msSaveBlob(blob, filename) + } else { + const elem = window.document.createElement('a') + elem.href = window.URL.createObjectURL(blob) + elem.download = filename + document.body.appendChild(elem) + elem.click() + document.body.removeChild(elem) + } +} From aa021b920942efd8075ca4e89cd92d50a1b21ed0 Mon Sep 17 00:00:00 2001 From: Kevin Serrano Date: Mon, 11 Sep 2017 16:40:45 -0700 Subject: [PATCH 054/140] Add some styling. --- ui/app/components/account-export.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/ui/app/components/account-export.js b/ui/app/components/account-export.js index d438c9ca5..53a7f0fa1 100644 --- a/ui/app/components/account-export.js +++ b/ui/app/components/account-export.js @@ -115,6 +115,9 @@ ExportAccountView.prototype.render = function () { }, 'Done'), h('button', { onClick: () => exportAsFile(`MetaMask ${nickname} Private Key`, plainKey), + stlye: { + marginLeft: '10px', + }, }, 'Save as File'), ]) } From e7c1a11b4de100f1966f639c79632a53763a8dac Mon Sep 17 00:00:00 2001 From: Kevin Serrano Date: Mon, 11 Sep 2017 16:48:02 -0700 Subject: [PATCH 055/140] Add ability to save seed words as file. --- ui/app/keychains/hd/create-vault-complete.js | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/ui/app/keychains/hd/create-vault-complete.js b/ui/app/keychains/hd/create-vault-complete.js index c32751fff..745990351 100644 --- a/ui/app/keychains/hd/create-vault-complete.js +++ b/ui/app/keychains/hd/create-vault-complete.js @@ -3,6 +3,7 @@ const Component = require('react').Component const connect = require('react-redux').connect const h = require('react-hyperscript') const actions = require('../../actions') +const exportAsFile = require('../../util').exportAsFile module.exports = connect(mapStateToProps)(CreateVaultCompleteScreen) @@ -65,8 +66,17 @@ CreateVaultCompleteScreen.prototype.render = function () { style: { margin: '24px', fontSize: '0.9em', + marginBottom: '10px', }, }, 'I\'ve copied it somewhere safe'), + + h('button.primary', { + onClick: () => exportAsFile(`MetaMask Seed Words`, seed), + style: { + margin: '10px', + fontSize: '0.9em', + }, + }, 'Save Seed Words As File'), ]) ) } From 1a0c465dc96ba31c2114626e8cb8d577b5a28e9a Mon Sep 17 00:00:00 2001 From: Kevin Serrano Date: Mon, 11 Sep 2017 16:48:18 -0700 Subject: [PATCH 056/140] Change the changelog --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0d41742be..44aae4cba 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,9 @@ ## Current Master +- Add ability to export private keys as a file. +- Add ability to export seed words as a file. + ## 3.10.0 2017-9-11 - Readded loose keyring label back into the account list. From 213af0cd6c84366b76a7ba5206fa5a6b79880028 Mon Sep 17 00:00:00 2001 From: Kevin Serrano Date: Mon, 11 Sep 2017 16:52:35 -0700 Subject: [PATCH 057/140] Fix typo. --- ui/app/components/account-export.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ui/app/components/account-export.js b/ui/app/components/account-export.js index 53a7f0fa1..32b103c86 100644 --- a/ui/app/components/account-export.js +++ b/ui/app/components/account-export.js @@ -114,10 +114,10 @@ ExportAccountView.prototype.render = function () { onClick: () => this.props.dispatch(actions.backToAccountDetail(this.props.address)), }, 'Done'), h('button', { - onClick: () => exportAsFile(`MetaMask ${nickname} Private Key`, plainKey), - stlye: { + style: { marginLeft: '10px', }, + onClick: () => exportAsFile(`MetaMask ${nickname} Private Key`, plainKey), }, 'Save as File'), ]) } From 45498452868ab82c7678086275bd4d35536ef753 Mon Sep 17 00:00:00 2001 From: kumavis Date: Mon, 11 Sep 2017 22:24:19 -0700 Subject: [PATCH 058/140] ci - deps - download latest chrome and firefox --- circle.yml | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/circle.yml b/circle.yml index e81e1bcaa..f5da6857d 100644 --- a/circle.yml +++ b/circle.yml @@ -3,4 +3,15 @@ machine: version: 8.1.4 test: override: - - "npm run ci" \ No newline at end of file + - "npm run ci" +dependencies: + pre: + - sudo apt-get update + # get latest stable firefox + - sudo apt-get install firefox + - firefox_cmd=`which firefox`; sudo rm -f $firefox_cmd; sudo ln -s `which firefox.ubuntu` $firefox_cmd + # get latest stable chrome + - wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | sudo apt-key add - + - sudo sh -c 'echo "deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main" >> /etc/apt/sources.list.d/google.list' + - sudo apt-get update + - sudo apt-get install google-chrome-stable \ No newline at end of file From 19d6618c04196f392a455823d1929025d40564fa Mon Sep 17 00:00:00 2001 From: kumavis Date: Mon, 11 Sep 2017 16:21:58 -0700 Subject: [PATCH 059/140] test - integration - build - use pump + log bundling information --- test/integration/index.js | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/test/integration/index.js b/test/integration/index.js index e089fc39b..8dc8a408f 100644 --- a/test/integration/index.js +++ b/test/integration/index.js @@ -1,5 +1,6 @@ const fs = require('fs') const path = require('path') +const pump = require('pump') const browserify = require('browserify') const tests = fs.readdirSync(path.join(__dirname, 'lib')) const bundlePath = path.join(__dirname, 'bundle.js') @@ -9,11 +10,16 @@ const b = browserify() const writeStream = fs.createWriteStream(bundlePath) tests.forEach(function (fileName) { - b.add(path.join(__dirname, 'lib', fileName)) + const filePath = path.join(__dirname, 'lib', fileName) + console.log(`bundling test "${filePath}"`) + b.add(filePath) }) -b.bundle() -.pipe(writeStream) -.on('error', (err) => { - throw err -}) +pump( + b.bundle(), + writeStream, + (err) => { + if (err) throw err + console.log('bundle completed.') + } +) \ No newline at end of file From ca035743c2a89b90b5b263bf3125cbed1175c4b2 Mon Sep 17 00:00:00 2001 From: kumavis Date: Mon, 11 Sep 2017 16:22:50 -0700 Subject: [PATCH 060/140] ci - dont attempt to submit coveralls if not configured --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 9afc181a3..eb0b57bb7 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,7 @@ "test-unit": "METAMASK_ENV=test mocha --require test/helper.js --recursive \"test/unit/**/*.js\"", "single-test": "METAMASK_ENV=test mocha --require test/helper.js", "test-integration": "npm run buildMock && npm run buildCiUnits && testem ci -P 2", - "test-coverage": "nyc npm run test-unit && nyc report --reporter=text-lcov | coveralls", + "test-coverage": "nyc npm run test-unit && if [ $COVERALLS_REPO_TOKEN ]; then nyc report --reporter=text-lcov | coveralls; fi", "ci": "npm run lint && npm run test-coverage && npm run test-integration", "lint": "gulp lint", "buildCiUnits": "node test/integration/index.js", From d4a41e0277bb2a8905b546165a3b0de62d3407cb Mon Sep 17 00:00:00 2001 From: kumavis Date: Mon, 11 Sep 2017 16:32:07 -0700 Subject: [PATCH 061/140] test - integration - build - manually exit from test builder + add bundle destination log --- test/integration/index.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/integration/index.js b/test/integration/index.js index 8dc8a408f..144303dbb 100644 --- a/test/integration/index.js +++ b/test/integration/index.js @@ -20,6 +20,7 @@ pump( writeStream, (err) => { if (err) throw err - console.log('bundle completed.') + console.log(`Integration test build completed: "${bundlePath}"`) + process.exit(0) } ) \ No newline at end of file From e9daf0eb280064a81235d144c71d41c8f1535aac Mon Sep 17 00:00:00 2001 From: kumavis Date: Mon, 11 Sep 2017 17:10:30 -0700 Subject: [PATCH 062/140] test - integration - remove redundant testem scripts --- package.json | 1 - testem.yml | 1 - 2 files changed, 2 deletions(-) diff --git a/package.json b/package.json index eb0b57bb7..71b132b98 100644 --- a/package.json +++ b/package.json @@ -22,7 +22,6 @@ "ui": "npm run genStates && beefy ui-dev.js:bundle.js --live --open --index=./development/index.html --cwd ./", "mock": "beefy mock-dev.js:bundle.js --live --open --index=./development/index.html --cwd ./", "buildMock": "npm run genStates && browserify ./mock-dev.js -o ./development/bundle.js", - "testem": "npm run buildMock && testem", "announce": "node development/announcer.js", "generateNotice": "node notices/notice-generator.js", "deleteNotice": "node notices/notice-delete.js", diff --git a/testem.yml b/testem.yml index 2cf40f7f4..7923a2929 100644 --- a/testem.yml +++ b/testem.yml @@ -6,5 +6,4 @@ launch_in_ci: - Firefox framework: - qunit -before_tests: "npm run buildCiUnits" test_page: "test/integration/index.html" From 0e70366e9c31d3085d505f110b586418c72c217e Mon Sep 17 00:00:00 2001 From: kumavis Date: Mon, 11 Sep 2017 20:14:52 -0700 Subject: [PATCH 063/140] test - integration - drop testem for karma --- karma.conf.js | 61 +++++++++++++++++++++++++ mock-dev.js | 73 ++++++++++++++++-------------- package.json | 7 ++- test/integration/helpers.js | 7 --- test/integration/lib/first-time.js | 58 ++++++++++++++++++------ 5 files changed, 150 insertions(+), 56 deletions(-) create mode 100644 karma.conf.js delete mode 100644 test/integration/helpers.js diff --git a/karma.conf.js b/karma.conf.js new file mode 100644 index 000000000..8e6d55972 --- /dev/null +++ b/karma.conf.js @@ -0,0 +1,61 @@ +// Karma configuration +// Generated on Mon Sep 11 2017 18:45:48 GMT-0700 (PDT) + +module.exports = function(config) { + config.set({ + // base path that will be used to resolve all patterns (eg. files, exclude) + basePath: process.cwd(), + + browserConsoleLogOptions: { + terminal: false, + }, + + // frameworks to use + // available frameworks: https://npmjs.org/browse/keyword/karma-adapter + frameworks: ['qunit'], + + // list of files / patterns to load in the browser + files: [ + 'development/bundle.js', + 'test/integration/jquery-3.1.0.min.js', + 'test/integration/bundle.js', + { pattern: 'dist/chrome/images/**/*.*', watched: false, included: false, served: true }, + { pattern: 'dist/chrome/fonts/**/*.*', watched: false, included: false, served: true }, + ], + + proxies: { + '/images/': '/base/dist/chrome/images/', + '/fonts/': '/base/dist/chrome/fonts/', + }, + + // test results reporter to use + // possible values: 'dots', 'progress' + // available reporters: https://npmjs.org/browse/keyword/karma-reporter + reporters: ['progress'], + + // web server port + port: 9876, + + // enable / disable colors in the output (reporters and logs) + colors: true, + + // level of logging + // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG + logLevel: config.LOG_INFO, + + // enable / disable watching file and executing tests whenever any file changes + autoWatch: false, + + // start these browsers + // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher + browsers: ['Chrome', 'Firefox'], + + // Continuous Integration mode + // if true, Karma captures browsers, runs the tests and exits + singleRun: true, + + // Concurrency level + // how many browser should be started simultaneous + concurrency: Infinity + }) +} diff --git a/mock-dev.js b/mock-dev.js index 8e1923a82..b6652bdf7 100644 --- a/mock-dev.js +++ b/mock-dev.js @@ -85,40 +85,47 @@ actions.update = function(stateName) { var css = MetaMaskUiCss() injectCss(css) -const container = document.querySelector('#app-content') - // parse opts var store = configureStore(firstState) // start app -render( - h('.super-dev-container', [ - - h('button', { - onClick: (ev) => { - ev.preventDefault() - store.dispatch(actions.update('terms')) - }, - style: { - margin: '19px 19px 0px 19px', - }, - }, 'Reset State'), - - h(Selector, { actions, selectedKey: selectedView, states, store }), - - h('.mock-app-root', { - style: { - height: '500px', - width: '360px', - boxShadow: 'grey 0px 2px 9px', - margin: '20px', - }, - }, [ - h(Root, { - store: store, - }), - ]), - - ] -), container) - +startApp() + +function startApp(){ + const body = document.body + const container = document.createElement('div') + container.id = 'app-content' + body.appendChild(container) + console.log('container', container) + + render( + h('.super-dev-container', [ + + h('button', { + onClick: (ev) => { + ev.preventDefault() + store.dispatch(actions.update('terms')) + }, + style: { + margin: '19px 19px 0px 19px', + }, + }, 'Reset State'), + + h(Selector, { actions, selectedKey: selectedView, states, store }), + + h('.mock-app-root', { + style: { + height: '500px', + width: '360px', + boxShadow: 'grey 0px 2px 9px', + margin: '20px', + }, + }, [ + h(Root, { + store: store, + }), + ]), + + ] + ), container) +} diff --git a/package.json b/package.json index 71b132b98..9d267bde6 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,7 @@ "test": "npm run lint && npm run test-unit && npm run test-integration", "test-unit": "METAMASK_ENV=test mocha --require test/helper.js --recursive \"test/unit/**/*.js\"", "single-test": "METAMASK_ENV=test mocha --require test/helper.js", - "test-integration": "npm run buildMock && npm run buildCiUnits && testem ci -P 2", + "test-integration": "npm run buildMock && npm run buildCiUnits && karma start", "test-coverage": "nyc npm run test-unit && if [ $COVERALLS_REPO_TOKEN ]; then nyc report --reporter=text-lcov | coveralls; fi", "ci": "npm run lint && npm run test-coverage && npm run test-integration", "lint": "gulp lint", @@ -171,6 +171,11 @@ "jsdom-global": "^3.0.2", "jshint-stylish": "~2.2.1", "json-rpc-engine": "^3.0.1", + "karma": "^1.7.1", + "karma-chrome-launcher": "^2.2.0", + "karma-cli": "^1.0.1", + "karma-firefox-launcher": "^1.0.1", + "karma-qunit": "^1.2.1", "lodash.assign": "^4.0.6", "mocha": "^3.4.2", "mocha-eslint": "^4.0.0", diff --git a/test/integration/helpers.js b/test/integration/helpers.js deleted file mode 100644 index 10cd74e64..000000000 --- a/test/integration/helpers.js +++ /dev/null @@ -1,7 +0,0 @@ -function wait(time) { - return new Promise(function (resolve, reject) { - setTimeout(function () { - resolve() - }, time * 3 || 1500) - }) -} diff --git a/test/integration/lib/first-time.js b/test/integration/lib/first-time.js index cc56b3704..c5ecfef95 100644 --- a/test/integration/lib/first-time.js +++ b/test/integration/lib/first-time.js @@ -10,26 +10,46 @@ QUnit.test('render init screen', (assert) => { }) }) +<<<<<<< HEAD +======= +// QUnit.testDone(({ module, name, total, passed, failed, skipped, todo, runtime }) => { +// if (failed > 0) { +// const app = $('iframe').contents()[0].documentElement +// console.warn('Test failures - dumping DOM:') +// console.log(app.innerHTML) +// } +// }) + +>>>>>>> 5c53bab... test - integration - drop testem for karma async function runFirstTimeUsageTest(assert, done) { - await wait() - const app = $('iframe').contents().find('#app-content .mock-app-root') + await timeout() + + const app = $('#app-content .mock-app-root') const recurseNotices = async () => { const button = app.find('button') if (button.html() === 'Accept') { const termsPage = app.find('.markdown')[0] termsPage.scrollTop = termsPage.scrollHeight - await wait() + await timeout() button.click() +<<<<<<< HEAD await wait() await recurseNotices() +======= + await timeout() +>>>>>>> 5c53bab... test - integration - drop testem for karma } else { await wait() } } +<<<<<<< HEAD await recurseNotices() +======= + await timeout() +>>>>>>> 5c53bab... test - integration - drop testem for karma // Scroll through terms const title = app.find('h1').text() @@ -41,13 +61,13 @@ async function runFirstTimeUsageTest(assert, done) { pwBox.value = PASSWORD confBox.value = PASSWORD - await wait() + await timeout() // create vault const createButton = app.find('button.primary')[0] createButton.click() - await wait(1500) + await timeout(1500) const created = app.find('h3')[0] assert.equal(created.textContent, 'Vault Created', 'Vault created screen') @@ -57,7 +77,7 @@ async function runFirstTimeUsageTest(assert, done) { assert.ok(button, 'button present') button.click() - await wait(1000) + await timeout(1000) const detail = app.find('.account-detail-section')[0] assert.ok(detail, 'Account detail section loaded.') @@ -65,7 +85,7 @@ async function runFirstTimeUsageTest(assert, done) { const sandwich = app.find('.sandwich-expando')[0] sandwich.click() - await wait() + await timeout() const menu = app.find('.menu-droppo')[0] const children = menu.children @@ -73,7 +93,7 @@ async function runFirstTimeUsageTest(assert, done) { assert.ok(lock, 'Lock menu item found') lock.click() - await wait(1000) + await timeout(1000) const pwBox2 = app.find('#password-box')[0] pwBox2.value = PASSWORD @@ -81,39 +101,47 @@ async function runFirstTimeUsageTest(assert, done) { const createButton2 = app.find('button.primary')[0] createButton2.click() - await wait(1000) + await timeout(1000) const detail2 = app.find('.account-detail-section')[0] assert.ok(detail2, 'Account detail section loaded again.') - await wait() + await timeout() // open account settings dropdown const qrButton = app.find('.fa.fa-ellipsis-h')[0] qrButton.click() - await wait(1000) + await timeout(1000) // qr code item const qrButton2 = app.find('.dropdown-menu-item')[1] qrButton2.click() - await wait(1000) + await timeout(1000) const qrHeader = app.find('.qr-header')[0] const qrContainer = app.find('#qr-container')[0] assert.equal(qrHeader.textContent, 'Account 1', 'Should show account label.') assert.ok(qrContainer, 'QR Container found') - await wait() + await timeout() const networkMenu = app.find('.network-indicator')[0] networkMenu.click() - await wait() + await timeout() const networkMenu2 = app.find('.network-indicator')[0] const children2 = networkMenu2.children children2.length[3] assert.ok(children2, 'All network options present') -} \ No newline at end of file +} + +function timeout(time) { + return new Promise(function (resolve, reject) { + setTimeout(function () { + resolve() + }, time * 3 || 1500) + }) +} From 48d21f4fca75e1f012ee881eb741a703051ee338 Mon Sep 17 00:00:00 2001 From: kumavis Date: Mon, 11 Sep 2017 22:34:16 -0700 Subject: [PATCH 064/140] tests - integration - fix bad cherry-pick --- test/integration/lib/first-time.js | 23 +++++++---------------- 1 file changed, 7 insertions(+), 16 deletions(-) diff --git a/test/integration/lib/first-time.js b/test/integration/lib/first-time.js index c5ecfef95..38a94e551 100644 --- a/test/integration/lib/first-time.js +++ b/test/integration/lib/first-time.js @@ -5,13 +5,11 @@ QUnit.module('first time usage') QUnit.test('render init screen', (assert) => { const done = assert.async() runFirstTimeUsageTest(assert).then(done).catch((err) => { - assert.notOk(err, 'Should not error') + assert.notOk(err, `Error was thrown: ${err.stack}`) done() }) }) -<<<<<<< HEAD -======= // QUnit.testDone(({ module, name, total, passed, failed, skipped, todo, runtime }) => { // if (failed > 0) { // const app = $('iframe').contents()[0].documentElement @@ -20,36 +18,29 @@ QUnit.test('render init screen', (assert) => { // } // }) ->>>>>>> 5c53bab... test - integration - drop testem for karma async function runFirstTimeUsageTest(assert, done) { await timeout() const app = $('#app-content .mock-app-root') - const recurseNotices = async () => { + // recurse notices + while (true) { const button = app.find('button') if (button.html() === 'Accept') { + // still notices to accept const termsPage = app.find('.markdown')[0] termsPage.scrollTop = termsPage.scrollHeight await timeout() button.click() -<<<<<<< HEAD - await wait() - await recurseNotices() -======= await timeout() ->>>>>>> 5c53bab... test - integration - drop testem for karma } else { - await wait() + // exit loop + break } } -<<<<<<< HEAD - await recurseNotices() -======= await timeout() ->>>>>>> 5c53bab... test - integration - drop testem for karma // Scroll through terms const title = app.find('h1').text() @@ -144,4 +135,4 @@ function timeout(time) { resolve() }, time * 3 || 1500) }) -} +} \ No newline at end of file From 22c7049f6c195e0b3775a1aa78f92fdbe2ac2559 Mon Sep 17 00:00:00 2001 From: kumavis Date: Mon, 11 Sep 2017 22:34:59 -0700 Subject: [PATCH 065/140] ci - remove old testem config --- testem.yml | 9 --------- 1 file changed, 9 deletions(-) delete mode 100644 testem.yml diff --git a/testem.yml b/testem.yml deleted file mode 100644 index 7923a2929..000000000 --- a/testem.yml +++ /dev/null @@ -1,9 +0,0 @@ -launch_in_dev: - - Chrome - - Firefox -launch_in_ci: - - Chrome - - Firefox -framework: - - qunit -test_page: "test/integration/index.html" From 40c105f639662f34f660c2f875fd728e0f5a0c15 Mon Sep 17 00:00:00 2001 From: kumavis Date: Mon, 11 Sep 2017 22:53:33 -0700 Subject: [PATCH 066/140] Rename add-to-firef.md to add-to-firefox.md This fixes the "How to add custom build to Firefox" link in the "README.md" on the root --- docs/{add-to-firef.md => add-to-firefox.md} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename docs/{add-to-firef.md => add-to-firefox.md} (100%) diff --git a/docs/add-to-firef.md b/docs/add-to-firefox.md similarity index 100% rename from docs/add-to-firef.md rename to docs/add-to-firefox.md From 985fb7b07c92a74712c44be24ec800676e8d2da8 Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Tue, 12 Sep 2017 14:10:26 +0000 Subject: [PATCH 067/140] chore(package): update babel-eslint to version 8.0.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 9afc181a3..9ec8cc243 100644 --- a/package.json +++ b/package.json @@ -138,7 +138,7 @@ }, "devDependencies": { "babel-core": "^6.24.1", - "babel-eslint": "^7.2.3", + "babel-eslint": "^8.0.0", "babel-plugin-transform-async-to-generator": "^6.24.1", "babel-plugin-transform-runtime": "^6.23.0", "babel-polyfill": "^6.23.0", From ad14e9338d2e08b0fd0e371e7a81e20f8f9e90f7 Mon Sep 17 00:00:00 2001 From: Kevin Serrano Date: Tue, 12 Sep 2017 09:34:48 -0700 Subject: [PATCH 068/140] Convert state logs into a download. --- ui/app/config.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/ui/app/config.js b/ui/app/config.js index 62785c49b..d64088ccb 100644 --- a/ui/app/config.js +++ b/ui/app/config.js @@ -5,7 +5,8 @@ const connect = require('react-redux').connect const actions = require('./actions') const currencies = require('./conversion.json').rows const validUrl = require('valid-url') -const copyToClipboard = require('copy-to-clipboard') +const exportAsFile = require('./util').exportAsFile + module.exports = connect(mapStateToProps)(ConfigScreen) @@ -110,9 +111,9 @@ ConfigScreen.prototype.render = function () { alignSelf: 'center', }, onClick (event) { - copyToClipboard(window.logState()) + exportAsFile('MetaMask State Logs', window.logState()) }, - }, 'Copy State Logs'), + }, 'Download State Logs'), ]), h('hr.horizontal-line'), From 111e67fa36edcce9477bc10844d0c9f8ff7b070e Mon Sep 17 00:00:00 2001 From: Kevin Serrano Date: Tue, 12 Sep 2017 09:35:24 -0700 Subject: [PATCH 069/140] Add to changelog. --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 44aae4cba..89eaefa79 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ - Add ability to export private keys as a file. - Add ability to export seed words as a file. +- Changed state logs to a file download than a clipboard copy. ## 3.10.0 2017-9-11 From 7810880f0c96ff8628cfd87bf7e6cb8145bb147a Mon Sep 17 00:00:00 2001 From: Kevin Serrano Date: Tue, 12 Sep 2017 12:23:21 -0700 Subject: [PATCH 070/140] Add specific error message for failed address checksum. --- ui/app/send.js | 5 +++++ ui/app/util.js | 7 +++++++ 2 files changed, 12 insertions(+) diff --git a/ui/app/send.js b/ui/app/send.js index a21a219eb..e59c1130e 100644 --- a/ui/app/send.js +++ b/ui/app/send.js @@ -262,6 +262,11 @@ SendTransactionScreen.prototype.onSubmit = function () { return this.props.dispatch(actions.displayWarning(message)) } + if ((util.isInvalidChecksumAddress(recipient))) { + message = 'Recipient address checksum is invalid.' + return this.props.dispatch(actions.displayWarning(message)) + } + if ((!util.isValidAddress(recipient) && !txData) || (!recipient && !txData)) { message = 'Recipient address is invalid.' return this.props.dispatch(actions.displayWarning(message)) diff --git a/ui/app/util.js b/ui/app/util.js index 1368ebf11..860a17224 100644 --- a/ui/app/util.js +++ b/ui/app/util.js @@ -37,6 +37,7 @@ module.exports = { bnTable: bnTable, isHex: isHex, exportAsFile: exportAsFile, + isInvalidChecksumAddress, } function valuesFor (obj) { @@ -66,6 +67,12 @@ function isValidAddress (address) { return (isAllOneCase(prefixed) && ethUtil.isValidAddress(prefixed)) || ethUtil.isValidChecksumAddress(prefixed) } +function isInvalidChecksumAddress (address) { + var prefixed = ethUtil.addHexPrefix(address) + if (address === '0x0000000000000000000000000000000000000000') return false + return ethUtil.isValidAddress(prefixed) && !ethUtil.isValidChecksumAddress(prefixed) +} + function isAllOneCase (address) { if (!address) return true var lower = address.toLowerCase() From 243be92ac165d028d532a02f5b32ff9edc526f23 Mon Sep 17 00:00:00 2001 From: Kevin Serrano Date: Tue, 12 Sep 2017 12:24:02 -0700 Subject: [PATCH 071/140] Changelogaroo. --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 89eaefa79..54ddb4a1b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ - Add ability to export private keys as a file. - Add ability to export seed words as a file. - Changed state logs to a file download than a clipboard copy. +- Add specific error for failed recipient address checksum. ## 3.10.0 2017-9-11 From ef6967325e43de26edc0d99ac5853804648024ae Mon Sep 17 00:00:00 2001 From: Kevin Serrano Date: Tue, 12 Sep 2017 12:52:13 -0700 Subject: [PATCH 072/140] Check if all lower or upper before doing checksum. --- ui/app/util.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/app/util.js b/ui/app/util.js index 860a17224..3f8b4dcc3 100644 --- a/ui/app/util.js +++ b/ui/app/util.js @@ -70,7 +70,7 @@ function isValidAddress (address) { function isInvalidChecksumAddress (address) { var prefixed = ethUtil.addHexPrefix(address) if (address === '0x0000000000000000000000000000000000000000') return false - return ethUtil.isValidAddress(prefixed) && !ethUtil.isValidChecksumAddress(prefixed) + return !isAllOneCase(prefixed) && !ethUtil.isValidChecksumAddress(prefixed) && ethUtil.isValidAddress(prefixed) } function isAllOneCase (address) { From 53a467cd1e2ab50168b06d36a98effcfd3db3a49 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Tue, 12 Sep 2017 15:06:19 -0700 Subject: [PATCH 073/140] Some progress --- app/scripts/controllers/balance.js | 28 ++++++++ app/scripts/controllers/balances.js | 108 ++++++++++++++++++++++++++++ app/scripts/metamask-controller.js | 8 ++- test/unit/pending-balance-test.js | 1 + 4 files changed, 144 insertions(+), 1 deletion(-) create mode 100644 app/scripts/controllers/balance.js create mode 100644 app/scripts/controllers/balances.js diff --git a/app/scripts/controllers/balance.js b/app/scripts/controllers/balance.js new file mode 100644 index 000000000..5dfe266e3 --- /dev/null +++ b/app/scripts/controllers/balance.js @@ -0,0 +1,28 @@ +const ObservableStore = require('obs-store') +const normalizeAddress = require('eth-sig-util').normalize +const extend = require('xtend') +const PendingBalanceCalculator = require('../lib/pending-balance-calculator') + +class BalanceController { + + constructor (opts = {}) { + const { address, ethQuery, txController } = opts + this.ethQuery = ethQuery + this.txController = txController + + const initState = extend({ + ethBalance: undefined, + }, opts.initState) + this.store = new ObservableStore(initState) + + const { getBalance, getPendingTransactions } = opts + this.balanceCalc = new PendingBalanceCalculator({ + getBalance, + getPendingTransactions, + }) + this.updateBalance() + } + +} + +module.exports = BalanceController diff --git a/app/scripts/controllers/balances.js b/app/scripts/controllers/balances.js new file mode 100644 index 000000000..b0b366628 --- /dev/null +++ b/app/scripts/controllers/balances.js @@ -0,0 +1,108 @@ +const ObservableStore = require('obs-store') +const normalizeAddress = require('eth-sig-util').normalize +const extend = require('xtend') +const BalanceController = require('./balance') + +class BalancesController { + + constructor (opts = {}) { + const { ethStore, txController } = opts + this.ethStore = ethStore + this.txController = txController + + const initState = extend({ + balances: [], + }, opts.initState) + this.store = new ObservableStore(initState) + + this._initBalanceUpdating() + } + + // PUBLIC METHODS + + setSelectedAddress (_address) { + return new Promise((resolve, reject) => { + const address = normalizeAddress(_address) + this.store.updateState({ selectedAddress: address }) + resolve() + }) + } + + getSelectedAddress (_address) { + return this.store.getState().selectedAddress + } + + addToken (rawAddress, symbol, decimals) { + const address = normalizeAddress(rawAddress) + const newEntry = { address, symbol, decimals } + + const tokens = this.store.getState().tokens + const previousIndex = tokens.find((token, index) => { + return token.address === address + }) + + if (previousIndex) { + tokens[previousIndex] = newEntry + } else { + tokens.push(newEntry) + } + + this.store.updateState({ tokens }) + return Promise.resolve() + } + + getTokens () { + return this.store.getState().tokens + } + + updateFrequentRpcList (_url) { + return this.addToFrequentRpcList(_url) + .then((rpcList) => { + this.store.updateState({ frequentRpcList: rpcList }) + return Promise.resolve() + }) + } + + setCurrentAccountTab (currentAccountTab) { + return new Promise((resolve, reject) => { + this.store.updateState({ currentAccountTab }) + resolve() + }) + } + + addToFrequentRpcList (_url) { + const rpcList = this.getFrequentRpcList() + const index = rpcList.findIndex((element) => { return element === _url }) + if (index !== -1) { + rpcList.splice(index, 1) + } + if (_url !== 'http://localhost:8545') { + rpcList.push(_url) + } + if (rpcList.length > 2) { + rpcList.shift() + } + return Promise.resolve(rpcList) + } + + getFrequentRpcList () { + return this.store.getState().frequentRpcList + } + // + // PRIVATE METHODS + // + _initBalanceUpdating () { + const store = this.ethStore.getState() + const balances = store.accounts + + for (let address in balances) { + let updater = new BalancesController({ + address, + ethQuery: this.ethQuery, + txController: this.txController, + }) + } + } +} + +module.exports = BalancesController diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index a007d6fc5..81e31a556 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -115,6 +115,12 @@ module.exports = class MetamaskController extends EventEmitter { }) this.txController.on('newUnaprovedTx', opts.showUnapprovedTx.bind(opts)) + // computed balances (accounting for pending transactions) + this.balancesController = new BalancesController({ + ethStore: this.ethStore, + txController: this.txController, + }) + // notices this.noticeController = new NoticeController({ initState: initState.NoticeController, @@ -647,4 +653,4 @@ module.exports = class MetamaskController extends EventEmitter { return Promise.resolve(rpcTarget) }) } -} \ No newline at end of file +} diff --git a/test/unit/pending-balance-test.js b/test/unit/pending-balance-test.js index 17be0306b..dde30fecc 100644 --- a/test/unit/pending-balance-test.js +++ b/test/unit/pending-balance-test.js @@ -96,3 +96,4 @@ function generateBalanceCalcWith (transactions, providerStub = zeroBn) { getPendingTransactions, }) } + From 7ed1fe90f84284dd714b6f36165ed4b853272bb1 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Tue, 12 Sep 2017 15:07:08 -0700 Subject: [PATCH 074/140] Fix support link --- ui/app/info.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/app/info.js b/ui/app/info.js index c69d83715..4c7d4cb4c 100644 --- a/ui/app/info.js +++ b/ui/app/info.js @@ -103,7 +103,7 @@ InfoScreen.prototype.render = function () { [ h('div.fa.fa-support', [ h('a.info', { - href: 'https://support.metamask.com', + href: 'https://support.metamask.io', target: '_blank', }, 'Visit our Support Center'), ]), From c8736b70b7613e869e15cb76a550cb5b031ba84f Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Tue, 12 Sep 2017 15:07:41 -0700 Subject: [PATCH 075/140] Bump changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 89eaefa79..f30c04985 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ - Add ability to export private keys as a file. - Add ability to export seed words as a file. - Changed state logs to a file download than a clipboard copy. +- Fix link to support center. ## 3.10.0 2017-9-11 From 962794d0257da705b2424351b7a187ccd4ec76ea Mon Sep 17 00:00:00 2001 From: Kevin Serrano Date: Tue, 12 Sep 2017 16:15:56 -0700 Subject: [PATCH 076/140] fixed position of tooltips to avoid overflow. --- ui/app/components/tooltip.js | 2 +- ui/app/components/transaction-list-item-icon.js | 2 +- ui/app/components/transaction-list-item.js | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/ui/app/components/tooltip.js b/ui/app/components/tooltip.js index edbc074bb..efab2c497 100644 --- a/ui/app/components/tooltip.js +++ b/ui/app/components/tooltip.js @@ -17,6 +17,6 @@ Tooltip.prototype.render = function () { return h(ReactTooltip, { position: position || 'left', title, - fixed: false, + fixed: true, }, children) } diff --git a/ui/app/components/transaction-list-item-icon.js b/ui/app/components/transaction-list-item-icon.js index 431054340..f442b05af 100644 --- a/ui/app/components/transaction-list-item-icon.js +++ b/ui/app/components/transaction-list-item-icon.js @@ -35,7 +35,7 @@ TransactionIcon.prototype.render = function () { case 'submitted': return h(Tooltip, { title: 'Pending', - position: 'bottom', + position: 'right', }, [ h('i.fa.fa-ellipsis-h', { style: { diff --git a/ui/app/components/transaction-list-item.js b/ui/app/components/transaction-list-item.js index 5d5d0bcc5..0e5c0b5a3 100644 --- a/ui/app/components/transaction-list-item.js +++ b/ui/app/components/transaction-list-item.js @@ -65,7 +65,7 @@ TransactionListItem.prototype.render = function () { h(Tooltip, { title: 'Transaction Number', - position: 'bottom', + position: 'right', }, [ h('span', { style: { From 5305d414b5901ff68ee58a3eca182af9c69debb0 Mon Sep 17 00:00:00 2001 From: Kevin Serrano Date: Tue, 12 Sep 2017 16:16:29 -0700 Subject: [PATCH 077/140] changelog. --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index f30c04985..4fb81c350 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ - Add ability to export seed words as a file. - Changed state logs to a file download than a clipboard copy. - Fix link to support center. +- Fixed tooltip icon locations to avoid overflow. ## 3.10.0 2017-9-11 From a265144176658220c5d8279ecb18c3ac0810e2c2 Mon Sep 17 00:00:00 2001 From: kumavis Date: Wed, 13 Sep 2017 10:21:00 -0700 Subject: [PATCH 078/140] metamask cont - standardize multiplex stream naming --- app/scripts/metamask-controller.js | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index 735fc4af0..b28f5042a 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -346,23 +346,23 @@ module.exports = class MetamaskController extends EventEmitter { } // setup multiplexing - const mx = setupMultiplex(connectionStream) + const mux = setupMultiplex(connectionStream) // connect features - this.setupProviderConnection(mx.createStream('provider'), originDomain) - this.setupPublicConfig(mx.createStream('publicConfig')) + this.setupProviderConnection(mux.createStream('provider'), originDomain) + this.setupPublicConfig(mux.createStream('publicConfig')) } setupTrustedCommunication (connectionStream, originDomain) { // setup multiplexing - const mx = setupMultiplex(connectionStream) + const mux = setupMultiplex(connectionStream) // connect features - this.setupControllerConnection(mx.createStream('controller')) - this.setupProviderConnection(mx.createStream('provider'), originDomain) + this.setupControllerConnection(mux.createStream('controller')) + this.setupProviderConnection(mux.createStream('provider'), originDomain) } sendPhishingWarning (connectionStream, hostname) { - const mx = setupMultiplex(connectionStream) - const phishingStream = mx.createStream('phishing') + const mux = setupMultiplex(connectionStream) + const phishingStream = mux.createStream('phishing') phishingStream.write({ hostname }) } From 96d1175834bb5d400f6a70d228cebbd23bded4db Mon Sep 17 00:00:00 2001 From: kumavis Date: Wed, 13 Sep 2017 10:28:29 -0700 Subject: [PATCH 079/140] debug - prefer logger over console --- app/scripts/background.js | 8 ++++---- app/scripts/metamask-controller.js | 10 +++++----- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/app/scripts/background.js b/app/scripts/background.js index f077ca7a8..1b96d68b5 100644 --- a/app/scripts/background.js +++ b/app/scripts/background.js @@ -1,6 +1,8 @@ const urlUtil = require('url') const endOfStream = require('end-of-stream') const pipe = require('pump') +const log = require('loglevel') +const extension = require('extensionizer') const LocalStorageStore = require('obs-store/lib/localStorage') const storeTransform = require('obs-store/lib/transform') const ExtensionPlatform = require('./platforms/extension') @@ -9,13 +11,11 @@ const migrations = require('./migrations/') const PortStream = require('./lib/port-stream.js') const NotificationManager = require('./lib/notification-manager.js') const MetamaskController = require('./metamask-controller') -const extension = require('extensionizer') const firstTimeState = require('./first-time-state') const STORAGE_KEY = 'metamask-config' const METAMASK_DEBUG = 'GULP_METAMASK_DEBUG' -const log = require('loglevel') window.log = log log.setDefaultLevel(METAMASK_DEBUG ? 'debug' : 'warn') @@ -29,12 +29,12 @@ let popupIsOpen = false const diskStore = new LocalStorageStore({ storageKey: STORAGE_KEY }) // initialization flow -initialize().catch(console.error) +initialize().catch(log.error) async function initialize () { const initState = await loadStateFromPersistence() await setupController(initState) - console.log('MetaMask initialization complete.') + log.debug('MetaMask initialization complete.') } // diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index b28f5042a..0c9602568 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -340,7 +340,7 @@ module.exports = class MetamaskController extends EventEmitter { setupUntrustedCommunication (connectionStream, originDomain) { // Check if new connection is blacklisted if (this.blacklistController.checkForPhishing(originDomain)) { - console.log('MetaMask - sending phishing warning for', originDomain) + log.debug('MetaMask - sending phishing warning for', originDomain) this.sendPhishingWarning(connectionStream, originDomain) return } @@ -374,7 +374,7 @@ module.exports = class MetamaskController extends EventEmitter { dnode, outStream, (err) => { - if (err) console.error(err) + if (err) log.error(err) } ) dnode.on('remote', (remote) => { @@ -402,7 +402,7 @@ module.exports = class MetamaskController extends EventEmitter { providerStream, outStream, (err) => { - if (err) console.error(err) + if (err) log.error(err) } ) @@ -416,7 +416,7 @@ module.exports = class MetamaskController extends EventEmitter { function loggerMiddleware (req, res, next, end) { next((cb) => { if (res.error) { - console.error('Error in RPC response:\n', res) + log.error('Error in RPC response:\n', res) } if (req.isMetamaskInternal) return log.info(`RPC (${originDomain}):`, req, '->', res) @@ -441,7 +441,7 @@ module.exports = class MetamaskController extends EventEmitter { this.publicConfigStore, outStream, (err) => { - if (err) console.error(err) + if (err) log.error(err) } ) } From e4d7fb244790d547b03d18763aa1d8e501d88b89 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Wed, 13 Sep 2017 11:39:39 -0700 Subject: [PATCH 080/140] Add state-labeled events to allow subscribing to any transaction's state change --- app/scripts/controllers/transactions.js | 1 + 1 file changed, 1 insertion(+) diff --git a/app/scripts/controllers/transactions.js b/app/scripts/controllers/transactions.js index fb3be6073..59a3f5329 100644 --- a/app/scripts/controllers/transactions.js +++ b/app/scripts/controllers/transactions.js @@ -434,6 +434,7 @@ module.exports = class TransactionController extends EventEmitter { const txMeta = this.getTx(txId) txMeta.status = status this.emit(`${txMeta.id}:${status}`, txId) + this.emit(`${status}`, txId) if (status === 'submitted' || status === 'rejected') { this.emit(`${txMeta.id}:finished`, txMeta) } From 06153dd47d16c2e3c6eed7470501d7534c29cbd4 Mon Sep 17 00:00:00 2001 From: Kevin Serrano Date: Wed, 13 Sep 2017 12:17:42 -0700 Subject: [PATCH 081/140] Add warning of higher failure risk since app proposed gasLimit. --- ui/app/components/pending-tx.js | 64 ++++++++++++++++++++------------- 1 file changed, 39 insertions(+), 25 deletions(-) diff --git a/ui/app/components/pending-tx.js b/ui/app/components/pending-tx.js index 3e53d47f9..37b242728 100644 --- a/ui/app/components/pending-tx.js +++ b/ui/app/components/pending-tx.js @@ -52,7 +52,8 @@ PendingTx.prototype.render = function () { const gas = txParams.gas const gasBn = hexToBn(gas) const gasLimit = new BN(parseInt(blockGasLimit)) - const safeGasLimit = this.bnMultiplyByFraction(gasLimit, 19, 20).toString(10) + const safeGasLimitBN = this.bnMultiplyByFraction(gasLimit, 19, 20) + const safeGasLimit = safeGasLimitBN.toString(10) // Gas Price const gasPrice = txParams.gasPrice || MIN_GAS_PRICE_BN.toString(16) @@ -66,6 +67,8 @@ PendingTx.prototype.render = function () { const balanceBn = hexToBn(balance) const insufficientBalance = balanceBn.lt(maxCost) + const dangerousGasLimit = gasBn.gte(safeGasLimitBN) + const gasLimitSpecified = txMeta.gasLimitSpecified const buyDisabled = insufficientBalance || !this.state.valid || !isValidAddress || this.state.submitting const showRejectAll = props.unconfTxListLength > 1 @@ -263,33 +266,44 @@ PendingTx.prototype.render = function () { text-transform: uppercase; } `), + h('.cell.row', { + style: { + textAlign: 'center', + }, + }, [ + txMeta.simulationFails ? + h('.error', { + style: { + fontSize: '0.9em', + }, + }, 'Transaction Error. Exception thrown in contract code.') + : null, - txMeta.simulationFails ? - h('.error', { - style: { - marginLeft: 50, - fontSize: '0.9em', - }, - }, 'Transaction Error. Exception thrown in contract code.') - : null, + !isValidAddress ? + h('.error', { + style: { + fontSize: '0.9em', + }, + }, 'Recipient address is invalid. Sending this transaction will result in a loss of ETH.') + : null, - !isValidAddress ? - h('.error', { - style: { - marginLeft: 50, - fontSize: '0.9em', - }, - }, 'Recipient address is invalid. Sending this transaction will result in a loss of ETH.') - : null, + insufficientBalance ? + h('span.error', { + style: { + fontSize: '0.9em', + }, + }, 'Insufficient balance for transaction') + : null, + + (dangerousGasLimit && gasLimitSpecified) ? + h('span.error', { + style: { + fontSize: '0.9em', + }, + }, 'Gas limit set dangerously high. Approving this transaction is likely to fail.') + : null, + ]), - insufficientBalance ? - h('span.error', { - style: { - marginLeft: 50, - fontSize: '0.9em', - }, - }, 'Insufficient balance for transaction') - : null, // send + cancel h('.flex-row.flex-space-around.conf-buttons', { From 6e725b123b878da12494904b121237ee41af7f76 Mon Sep 17 00:00:00 2001 From: Kevin Serrano Date: Wed, 13 Sep 2017 12:22:08 -0700 Subject: [PATCH 082/140] Lower warning threshold for high tx fee to account for fluctuating blockGasLimits --- ui/app/components/pending-tx.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ui/app/components/pending-tx.js b/ui/app/components/pending-tx.js index 37b242728..ec2b15de4 100644 --- a/ui/app/components/pending-tx.js +++ b/ui/app/components/pending-tx.js @@ -53,6 +53,7 @@ PendingTx.prototype.render = function () { const gasBn = hexToBn(gas) const gasLimit = new BN(parseInt(blockGasLimit)) const safeGasLimitBN = this.bnMultiplyByFraction(gasLimit, 19, 20) + const saferGasLimitBN = this.bnMultiplyByFraction(gasLimit, 18, 20) const safeGasLimit = safeGasLimitBN.toString(10) // Gas Price @@ -67,7 +68,7 @@ PendingTx.prototype.render = function () { const balanceBn = hexToBn(balance) const insufficientBalance = balanceBn.lt(maxCost) - const dangerousGasLimit = gasBn.gte(safeGasLimitBN) + const dangerousGasLimit = gasBn.gte(saferGasLimitBN) const gasLimitSpecified = txMeta.gasLimitSpecified const buyDisabled = insufficientBalance || !this.state.valid || !isValidAddress || this.state.submitting const showRejectAll = props.unconfTxListLength > 1 From 3d36d565afe908ca7c4424ff8a7d3659d0978aca Mon Sep 17 00:00:00 2001 From: Kevin Serrano Date: Wed, 13 Sep 2017 12:26:24 -0700 Subject: [PATCH 083/140] Bump changelog. --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index f30c04985..6f357c92c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ - Add ability to export seed words as a file. - Changed state logs to a file download than a clipboard copy. - Fix link to support center. +- Warn users when a dapp proposes a high gas limit (90% of blockGasLimit or higher) ## 3.10.0 2017-9-11 From a22a2586abcca35d2a4bc1a0407aeaacc55f0a27 Mon Sep 17 00:00:00 2001 From: Kevin Serrano Date: Wed, 13 Sep 2017 13:24:16 -0700 Subject: [PATCH 084/140] Haha silly me, only when gas is estimated and not explicit. --- ui/app/components/pending-tx.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/app/components/pending-tx.js b/ui/app/components/pending-tx.js index ec2b15de4..c3350fcc1 100644 --- a/ui/app/components/pending-tx.js +++ b/ui/app/components/pending-tx.js @@ -296,7 +296,7 @@ PendingTx.prototype.render = function () { }, 'Insufficient balance for transaction') : null, - (dangerousGasLimit && gasLimitSpecified) ? + (dangerousGasLimit && !gasLimitSpecified) ? h('span.error', { style: { fontSize: '0.9em', From 86cd4e4fedbea9639de33827733b4b85ef988bee Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Wed, 13 Sep 2017 14:20:19 -0700 Subject: [PATCH 085/140] Got pending balance updating correctly --- app/scripts/controllers/balance.js | 45 +++++++++++-- app/scripts/controllers/balances.js | 101 +++++++--------------------- app/scripts/metamask-controller.js | 4 ++ 3 files changed, 69 insertions(+), 81 deletions(-) diff --git a/app/scripts/controllers/balance.js b/app/scripts/controllers/balance.js index 5dfe266e3..0d4ab7d4f 100644 --- a/app/scripts/controllers/balance.js +++ b/app/scripts/controllers/balance.js @@ -2,12 +2,14 @@ const ObservableStore = require('obs-store') const normalizeAddress = require('eth-sig-util').normalize const extend = require('xtend') const PendingBalanceCalculator = require('../lib/pending-balance-calculator') +const BN = require('ethereumjs-util').BN class BalanceController { constructor (opts = {}) { - const { address, ethQuery, txController } = opts - this.ethQuery = ethQuery + const { address, ethStore, txController } = opts + this.address = address + this.ethStore = ethStore this.txController = txController const initState = extend({ @@ -17,10 +19,43 @@ class BalanceController { const { getBalance, getPendingTransactions } = opts this.balanceCalc = new PendingBalanceCalculator({ - getBalance, - getPendingTransactions, + getBalance: () => Promise.resolve(this._getBalance()), + getPendingTransactions: this._getPendingTransactions.bind(this), }) - this.updateBalance() + + this.registerUpdates() + } + + async updateBalance () { + const balance = await this.balanceCalc.getBalance() + this.store.updateState({ + ethBalance: balance, + }) + } + + registerUpdates () { + const update = this.updateBalance.bind(this) + this.txController.on('submitted', update) + this.txController.on('confirmed', update) + this.txController.on('failed', update) + this.txController.blockTracker.on('block', update) + } + + _getBalance () { + const store = this.ethStore.getState() + const balances = store.accounts + const entry = balances[this.address] + const balance = entry.balance + return balance ? new BN(balance.substring(2), 16) : new BN(0) + } + + _getPendingTransactions () { + const pending = this.txController.getFilteredTxList({ + from: this.address, + status: 'submitted', + err: undefined, + }) + return Promise.resolve(pending) } } diff --git a/app/scripts/controllers/balances.js b/app/scripts/controllers/balances.js index b0b366628..cf3c8a757 100644 --- a/app/scripts/controllers/balances.js +++ b/app/scripts/controllers/balances.js @@ -11,97 +11,46 @@ class BalancesController { this.txController = txController const initState = extend({ - balances: [], + computedBalances: {}, }, opts.initState) this.store = new ObservableStore(initState) this._initBalanceUpdating() } - // PUBLIC METHODS - - setSelectedAddress (_address) { - return new Promise((resolve, reject) => { - const address = normalizeAddress(_address) - this.store.updateState({ selectedAddress: address }) - resolve() - }) - } - - getSelectedAddress (_address) { - return this.store.getState().selectedAddress + _initBalanceUpdating () { + const store = this.ethStore.getState() + this.addAnyAccountsFromStore(store) + this.ethStore.subscribe(this.addAnyAccountsFromStore.bind(this)) } - addToken (rawAddress, symbol, decimals) { - const address = normalizeAddress(rawAddress) - const newEntry = { address, symbol, decimals } - - const tokens = this.store.getState().tokens - const previousIndex = tokens.find((token, index) => { - return token.address === address - }) + addAnyAccountsFromStore(store) { + const balances = store.accounts - if (previousIndex) { - tokens[previousIndex] = newEntry - } else { - tokens.push(newEntry) + for (let address in balances) { + this.trackAddressIfNotAlready(address) } - - this.store.updateState({ tokens }) - return Promise.resolve() - } - - getTokens () { - return this.store.getState().tokens - } - - updateFrequentRpcList (_url) { - return this.addToFrequentRpcList(_url) - .then((rpcList) => { - this.store.updateState({ frequentRpcList: rpcList }) - return Promise.resolve() - }) } - setCurrentAccountTab (currentAccountTab) { - return new Promise((resolve, reject) => { - this.store.updateState({ currentAccountTab }) - resolve() - }) - } - - addToFrequentRpcList (_url) { - const rpcList = this.getFrequentRpcList() - const index = rpcList.findIndex((element) => { return element === _url }) - if (index !== -1) { - rpcList.splice(index, 1) - } - if (_url !== 'http://localhost:8545') { - rpcList.push(_url) + trackAddressIfNotAlready (address) { + const state = this.store.getState() + if (!(address in state.computedBalances)) { + this.trackAddress(address) } - if (rpcList.length > 2) { - rpcList.shift() - } - return Promise.resolve(rpcList) - } - - getFrequentRpcList () { - return this.store.getState().frequentRpcList } - // - // PRIVATE METHODS - // - _initBalanceUpdating () { - const store = this.ethStore.getState() - const balances = store.accounts - for (let address in balances) { - let updater = new BalancesController({ - address, - ethQuery: this.ethQuery, - txController: this.txController, - }) - } + trackAddress (address) { + let updater = new BalanceController({ + address, + ethStore: this.ethStore, + txController: this.txController, + }) + updater.store.subscribe((accountBalance) => { + let newState = this.store.getState() + newState.computedBalances[address] = accountBalance + this.store.updateState(newState) + }) + updater.updateBalance() } } diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index 81e31a556..f2df45947 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -20,6 +20,7 @@ const BlacklistController = require('./controllers/blacklist') const MessageManager = require('./lib/message-manager') const PersonalMessageManager = require('./lib/personal-message-manager') const TransactionController = require('./controllers/transactions') +const BalancesController = require('./controllers/balances') const ConfigManager = require('./lib/config-manager') const nodeify = require('./lib/nodeify') const accountImporter = require('./account-import-strategies') @@ -174,6 +175,7 @@ module.exports = class MetamaskController extends EventEmitter { this.networkController.store.subscribe(this.sendUpdate.bind(this)) this.ethStore.subscribe(this.sendUpdate.bind(this)) this.txController.memStore.subscribe(this.sendUpdate.bind(this)) + this.balancesController.store.subscribe(this.sendUpdate.bind(this)) this.messageManager.memStore.subscribe(this.sendUpdate.bind(this)) this.personalMessageManager.memStore.subscribe(this.sendUpdate.bind(this)) this.keyringController.memStore.subscribe(this.sendUpdate.bind(this)) @@ -248,6 +250,7 @@ module.exports = class MetamaskController extends EventEmitter { const wallet = this.configManager.getWallet() const vault = this.keyringController.store.getState().vault const isInitialized = (!!wallet || !!vault) + return extend( { isInitialized, @@ -258,6 +261,7 @@ module.exports = class MetamaskController extends EventEmitter { this.messageManager.memStore.getState(), this.personalMessageManager.memStore.getState(), this.keyringController.memStore.getState(), + this.balancesController.store.getState(), this.preferencesController.store.getState(), this.addressBookController.store.getState(), this.currencyController.store.getState(), From 0ba649317592efced339980a729213876bc25a81 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Wed, 13 Sep 2017 14:31:48 -0700 Subject: [PATCH 086/140] Use computed balance for tx confirmation --- ui/app/components/pending-tx.js | 6 +++--- ui/app/conf-tx.js | 4 +++- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/ui/app/components/pending-tx.js b/ui/app/components/pending-tx.js index 3e53d47f9..1cc8daebe 100644 --- a/ui/app/components/pending-tx.js +++ b/ui/app/components/pending-tx.js @@ -33,7 +33,7 @@ function PendingTx () { PendingTx.prototype.render = function () { const props = this.props - const { currentCurrency, blockGasLimit } = props + const { currentCurrency, blockGasLimit, computedBalances } = props const conversionRate = props.conversionRate const txMeta = this.gatherTxMeta() @@ -42,8 +42,8 @@ PendingTx.prototype.render = function () { // Account Details const address = txParams.from || props.selectedAddress const identity = props.identities[address] || { address: address } - const account = props.accounts[address] - const balance = account ? account.balance : '0x0' + const account = computedBalances[address] + const balance = account ? account.ethBalance : '0x0' // recipient check const isValidAddress = !txParams.to || util.isValidAddress(txParams.to) diff --git a/ui/app/conf-tx.js b/ui/app/conf-tx.js index 1ee4166f7..8b93305eb 100644 --- a/ui/app/conf-tx.js +++ b/ui/app/conf-tx.js @@ -29,6 +29,7 @@ function mapStateToProps (state) { conversionRate: state.metamask.conversionRate, currentCurrency: state.metamask.currentCurrency, blockGasLimit: state.metamask.currentBlockGasLimit, + computedBalances: state.metamask.computedBalances, } } @@ -39,7 +40,7 @@ function ConfirmTxScreen () { ConfirmTxScreen.prototype.render = function () { const props = this.props - const { network, provider, unapprovedTxs, currentCurrency, + const { network, provider, unapprovedTxs, currentCurrency, computedBalances, unapprovedMsgs, unapprovedPersonalMsgs, conversionRate, blockGasLimit } = props var unconfTxList = txHelper(unapprovedTxs, unapprovedMsgs, unapprovedPersonalMsgs, network) @@ -104,6 +105,7 @@ ConfirmTxScreen.prototype.render = function () { currentCurrency, blockGasLimit, unconfTxListLength, + computedBalances, // Actions buyEth: this.buyEth.bind(this, txParams.from || props.selectedAddress), sendTransaction: this.sendTransaction.bind(this), From a01921758b25d151cfb1c47d7235f59291c29945 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Wed, 13 Sep 2017 15:02:05 -0700 Subject: [PATCH 087/140] Add computed balance to account detail view --- app/scripts/controllers/balance.js | 9 +++------ app/scripts/controllers/balances.js | 9 ++++++++- app/scripts/lib/pending-balance-calculator.js | 2 ++ app/scripts/metamask-controller.js | 4 ++++ ui/app/account-detail.js | 5 +++-- ui/app/conf-tx.js | 1 - 6 files changed, 20 insertions(+), 10 deletions(-) diff --git a/app/scripts/controllers/balance.js b/app/scripts/controllers/balance.js index 0d4ab7d4f..b4e72e751 100644 --- a/app/scripts/controllers/balance.js +++ b/app/scripts/controllers/balance.js @@ -1,6 +1,4 @@ const ObservableStore = require('obs-store') -const normalizeAddress = require('eth-sig-util').normalize -const extend = require('xtend') const PendingBalanceCalculator = require('../lib/pending-balance-calculator') const BN = require('ethereumjs-util').BN @@ -12,12 +10,11 @@ class BalanceController { this.ethStore = ethStore this.txController = txController - const initState = extend({ + const initState = { ethBalance: undefined, - }, opts.initState) + } this.store = new ObservableStore(initState) - const { getBalance, getPendingTransactions } = opts this.balanceCalc = new PendingBalanceCalculator({ getBalance: () => Promise.resolve(this._getBalance()), getPendingTransactions: this._getPendingTransactions.bind(this), @@ -46,7 +43,7 @@ class BalanceController { const balances = store.accounts const entry = balances[this.address] const balance = entry.balance - return balance ? new BN(balance.substring(2), 16) : new BN(0) + return balance ? new BN(balance.substring(2), 16) : undefined } _getPendingTransactions () { diff --git a/app/scripts/controllers/balances.js b/app/scripts/controllers/balances.js index cf3c8a757..89c2ca95d 100644 --- a/app/scripts/controllers/balances.js +++ b/app/scripts/controllers/balances.js @@ -1,5 +1,4 @@ const ObservableStore = require('obs-store') -const normalizeAddress = require('eth-sig-util').normalize const extend = require('xtend') const BalanceController = require('./balance') @@ -14,10 +13,17 @@ class BalancesController { computedBalances: {}, }, opts.initState) this.store = new ObservableStore(initState) + this.balances = {} this._initBalanceUpdating() } + updateAllBalances () { + for (let address in this.balances) { + this.balances[address].updateBalance() + } + } + _initBalanceUpdating () { const store = this.ethStore.getState() this.addAnyAccountsFromStore(store) @@ -50,6 +56,7 @@ class BalancesController { newState.computedBalances[address] = accountBalance this.store.updateState(newState) }) + this.balances[address] = updater updater.updateBalance() } } diff --git a/app/scripts/lib/pending-balance-calculator.js b/app/scripts/lib/pending-balance-calculator.js index 474ed3261..c66bffbbb 100644 --- a/app/scripts/lib/pending-balance-calculator.js +++ b/app/scripts/lib/pending-balance-calculator.js @@ -22,6 +22,8 @@ class PendingBalanceCalculator { const balance = results[0] const pending = results[1] + if (!balance) return undefined + const pendingValue = pending.reduce((total, tx) => { return total.add(this.valueFor(tx)) }, new BN(0)) diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index f2df45947..02c06ead2 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -121,6 +121,10 @@ module.exports = class MetamaskController extends EventEmitter { ethStore: this.ethStore, txController: this.txController, }) + this.networkController.on('networkDidChange', () => { + this.balancesController.updateAllBalances() + }) + this.balancesController.updateAllBalances() // notices this.noticeController = new NoticeController({ diff --git a/ui/app/account-detail.js b/ui/app/account-detail.js index 02089ecd0..90724dc3f 100644 --- a/ui/app/account-detail.js +++ b/ui/app/account-detail.js @@ -32,6 +32,7 @@ function mapStateToProps (state) { currentCurrency: state.metamask.currentCurrency, currentAccountTab: state.metamask.currentAccountTab, tokens: state.metamask.tokens, + computedBalances: state.metamask.computedBalances, } } @@ -45,7 +46,7 @@ AccountDetailScreen.prototype.render = function () { var selected = props.address || Object.keys(props.accounts)[0] var checksumAddress = selected && ethUtil.toChecksumAddress(selected) var identity = props.identities[selected] - var account = props.accounts[selected] + var account = props.computedBalances[selected] const { network, conversionRate, currentCurrency } = props return ( @@ -180,7 +181,7 @@ AccountDetailScreen.prototype.render = function () { }, [ h(EthBalance, { - value: account && account.balance, + value: account && account.ethBalance, conversionRate, currentCurrency, style: { diff --git a/ui/app/conf-tx.js b/ui/app/conf-tx.js index 8b93305eb..15fb9a59f 100644 --- a/ui/app/conf-tx.js +++ b/ui/app/conf-tx.js @@ -49,7 +49,6 @@ ConfirmTxScreen.prototype.render = function () { var txParams = txData.params || {} var isNotification = isPopupOrNotification() === 'notification' - log.info(`rendering a combined ${unconfTxList.length} unconf msg & txs`) if (unconfTxList.length === 0) return h(Loading, { isLoading: true }) From 245c0f0c2741d1dcb706faa93ff681333a40b9c8 Mon Sep 17 00:00:00 2001 From: kumavis Date: Wed, 13 Sep 2017 15:17:26 -0700 Subject: [PATCH 088/140] metamask controller - move middleware into seperate files --- app/scripts/lib/createLoggerMiddleware.js | 15 ++++++++ app/scripts/lib/createOriginMiddleware.js | 9 +++++ app/scripts/lib/createProviderMiddleware.js | 13 +++++++ app/scripts/metamask-controller.js | 41 ++++----------------- package.json | 2 +- 5 files changed, 45 insertions(+), 35 deletions(-) create mode 100644 app/scripts/lib/createLoggerMiddleware.js create mode 100644 app/scripts/lib/createOriginMiddleware.js create mode 100644 app/scripts/lib/createProviderMiddleware.js diff --git a/app/scripts/lib/createLoggerMiddleware.js b/app/scripts/lib/createLoggerMiddleware.js new file mode 100644 index 000000000..b92a965de --- /dev/null +++ b/app/scripts/lib/createLoggerMiddleware.js @@ -0,0 +1,15 @@ +// log rpc activity +module.exports = createLoggerMiddleware + +function createLoggerMiddleware({ origin }) { + return function loggerMiddleware (req, res, next, end) { + next((cb) => { + if (res.error) { + log.error('Error in RPC response:\n', res) + } + if (req.isMetamaskInternal) return + log.info(`RPC (${origin}):`, req, '->', res) + cb() + }) + } +} \ No newline at end of file diff --git a/app/scripts/lib/createOriginMiddleware.js b/app/scripts/lib/createOriginMiddleware.js new file mode 100644 index 000000000..f21d79512 --- /dev/null +++ b/app/scripts/lib/createOriginMiddleware.js @@ -0,0 +1,9 @@ +// append dapp origin domain to request +module.exports = createOriginMiddleware + +function createOriginMiddleware({ origin }) { + return function originMiddleware (req, res, next, end) { + req.origin = originDomain + next() + } +} \ No newline at end of file diff --git a/app/scripts/lib/createProviderMiddleware.js b/app/scripts/lib/createProviderMiddleware.js new file mode 100644 index 000000000..6dd192411 --- /dev/null +++ b/app/scripts/lib/createProviderMiddleware.js @@ -0,0 +1,13 @@ + +module.exports = createProviderMiddleware + +// forward requests to provider +function createProviderMiddleware({ provider }) { + return (req, res, next, end) => { + provider.sendAsync(req, (err, _res) => { + if (err) return end(err) + res.result = _res.result + end() + }) + } +} \ No newline at end of file diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index 0c9602568..f114d22f3 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -7,8 +7,12 @@ const ObservableStore = require('obs-store') const EthStore = require('./lib/eth-store') const EthQuery = require('eth-query') const RpcEngine = require('json-rpc-engine') +const debounce = require('debounce') const createEngineStream = require('json-rpc-middleware-stream/engineStream') const createFilterMiddleware = require('eth-json-rpc-filters') +const createOriginMiddleware = require('./lib/createOriginMiddleware') +const createLoggerMiddleware = require('./lib/createLoggerMiddleware') +const createProviderMiddleware = require('./lib/createProviderMiddleware') const setupMultiplex = require('./lib/stream-utils.js').setupMultiplex const KeyringController = require('./keyring-controller') const NetworkController = require('./controllers/network') @@ -26,8 +30,6 @@ const ConfigManager = require('./lib/config-manager') const nodeify = require('./lib/nodeify') const accountImporter = require('./account-import-strategies') const getBuyEthUrl = require('./lib/buy-eth-url') -const debounce = require('debounce') - const version = require('../manifest.json').version module.exports = class MetamaskController extends EventEmitter { @@ -384,11 +386,11 @@ module.exports = class MetamaskController extends EventEmitter { }) } - setupProviderConnection (outStream, originDomain) { + setupProviderConnection (outStream, origin) { // setup json rpc engine stack const engine = new RpcEngine() - engine.push(originMiddleware) - engine.push(loggerMiddleware) + engine.push(createOriginMiddleware({ origin })) + engine.push(createLoggerMiddleware({ origin })) engine.push(createFilterMiddleware({ provider: this.provider, blockTracker: this.blockTracker, @@ -405,35 +407,6 @@ module.exports = class MetamaskController extends EventEmitter { if (err) log.error(err) } ) - - // append dapp origin domain to request - function originMiddleware (req, res, next, end) { - req.origin = originDomain - next() - } - - // log rpc activity - function loggerMiddleware (req, res, next, end) { - next((cb) => { - if (res.error) { - log.error('Error in RPC response:\n', res) - } - if (req.isMetamaskInternal) return - log.info(`RPC (${originDomain}):`, req, '->', res) - cb() - }) - } - - // forward requests to provider - function createProviderMiddleware({ provider }) { - return (req, res, next, end) => { - provider.sendAsync(req, (err, _res) => { - if (err) return end(err) - res.result = _res.result - end() - }) - } - } } setupPublicConfig (outStream) { diff --git a/package.json b/package.json index b241ccfc6..3f9d9c538 100644 --- a/package.json +++ b/package.json @@ -68,7 +68,7 @@ "eth-bin-to-ops": "^1.0.1", "eth-contract-metadata": "^1.1.4", "eth-hd-keyring": "^1.1.1", - "eth-json-rpc-filters": "^1.0.2", + "eth-json-rpc-filters": "^1.1.0", "eth-phishing-detect": "^1.1.4", "eth-query": "^2.1.2", "eth-sig-util": "^1.2.2", From 765ef640610584a3fdd7fa0ef716f6c7a553407c Mon Sep 17 00:00:00 2001 From: kumavis Date: Wed, 13 Sep 2017 15:19:44 -0700 Subject: [PATCH 089/140] metamask controller - destroy filter polyfill on disconnect --- app/scripts/metamask-controller.js | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index f114d22f3..fef16c3a9 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -389,12 +389,16 @@ module.exports = class MetamaskController extends EventEmitter { setupProviderConnection (outStream, origin) { // setup json rpc engine stack const engine = new RpcEngine() - engine.push(createOriginMiddleware({ origin })) - engine.push(createLoggerMiddleware({ origin })) - engine.push(createFilterMiddleware({ + + // create filter polyfill middleware + const filterMiddleware = createFilterMiddleware({ provider: this.provider, blockTracker: this.blockTracker, - })) + }) + + engine.push(createOriginMiddleware({ origin })) + engine.push(createLoggerMiddleware({ origin })) + engine.push(filterMiddleware) engine.push(createProviderMiddleware({ provider: this.provider })) // setup connection @@ -404,6 +408,8 @@ module.exports = class MetamaskController extends EventEmitter { providerStream, outStream, (err) => { + // cleanup filter polyfill middleware + filterMiddleware.destroy() if (err) log.error(err) } ) From c9585f166f3aebea29e7214026a942eb18e4884a Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Wed, 13 Sep 2017 15:21:18 -0700 Subject: [PATCH 090/140] Fix test --- test/unit/components/pending-tx-test.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/test/unit/components/pending-tx-test.js b/test/unit/components/pending-tx-test.js index 22a98bc93..fdade1042 100644 --- a/test/unit/components/pending-tx-test.js +++ b/test/unit/components/pending-tx-test.js @@ -34,10 +34,15 @@ describe('PendingTx', function () { const renderer = ReactTestUtils.createRenderer() const newGasPrice = '0x77359400' + const computedBalances = {} + computedBalances[Object.keys(identities)[0]] = { + ethBalance: '0x00000000000000056bc75e2d63100000', + } const props = { identities, accounts: identities, txData, + computedBalances, sendTransaction: (txMeta, event) => { // Assert changes: const result = ethUtil.addHexPrefix(txMeta.txParams.gasPrice) From 6ba448edaf64d1122ab0b5c8af21f9806164c309 Mon Sep 17 00:00:00 2001 From: kumavis Date: Wed, 13 Sep 2017 15:26:00 -0700 Subject: [PATCH 091/140] changelog - add note of filter memory leak fix --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 89eaefa79..e3cf3505a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ - Add ability to export private keys as a file. - Add ability to export seed words as a file. - Changed state logs to a file download than a clipboard copy. +- Fixed a long standing memory leak associated with filters installed by dapps ## 3.10.0 2017-9-11 From d7097db0221cf6b6958ceb65e9ce4c893a8dfb61 Mon Sep 17 00:00:00 2001 From: kumavis Date: Wed, 13 Sep 2017 15:29:44 -0700 Subject: [PATCH 092/140] createOriginMiddleware - fix var name --- app/scripts/lib/createOriginMiddleware.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/scripts/lib/createOriginMiddleware.js b/app/scripts/lib/createOriginMiddleware.js index f21d79512..e1e097cc4 100644 --- a/app/scripts/lib/createOriginMiddleware.js +++ b/app/scripts/lib/createOriginMiddleware.js @@ -3,7 +3,7 @@ module.exports = createOriginMiddleware function createOriginMiddleware({ origin }) { return function originMiddleware (req, res, next, end) { - req.origin = originDomain + req.origin = origin next() } } \ No newline at end of file From dd90e8e364a7023431be081d293d10129b76422c Mon Sep 17 00:00:00 2001 From: kumavis Date: Wed, 13 Sep 2017 16:04:48 -0700 Subject: [PATCH 093/140] tests - break out coveralls npm script --- package.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 12f79ba35..d618983bd 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,8 @@ "test-unit": "METAMASK_ENV=test mocha --require test/helper.js --recursive \"test/unit/**/*.js\"", "single-test": "METAMASK_ENV=test mocha --require test/helper.js", "test-integration": "npm run buildMock && npm run buildCiUnits && karma start", - "test-coverage": "nyc npm run test-unit && if [ $COVERALLS_REPO_TOKEN ]; then nyc report --reporter=text-lcov | coveralls; fi", + "test-coverage": "nyc npm run test-unit && npm run coveralls-upload", + "coveralls-upload": "if [ $COVERALLS_REPO_TOKEN ]; then nyc report --reporter=text-lcov | coveralls; fi", "ci": "npm run lint && npm run test-coverage && npm run test-integration", "lint": "gulp lint", "buildCiUnits": "node test/integration/index.js", From 56db3999adeaec1733218173b260bf74fa63ccbe Mon Sep 17 00:00:00 2001 From: kumavis Date: Wed, 13 Sep 2017 16:45:31 -0700 Subject: [PATCH 094/140] tests - start migrating mascara tests to karma --- mascara/src/ui.js | 9 +++++++-- mascara/test/test-ui.js | 13 +++++++++++++ mascara/test/window-load.js | 5 ----- mock-dev.js | 1 - package.json | 11 ++++++----- karma.conf.js => test/base.conf.js | 6 ++---- test/mascara.conf.js | 14 ++++++++++++++ test/together.conf.js | 8 ++++++++ 8 files changed, 50 insertions(+), 17 deletions(-) create mode 100644 mascara/test/test-ui.js delete mode 100644 mascara/test/window-load.js rename karma.conf.js => test/base.conf.js (95%) create mode 100644 test/mascara.conf.js create mode 100644 test/together.conf.js diff --git a/mascara/src/ui.js b/mascara/src/ui.js index 5f9be542f..5662270c1 100644 --- a/mascara/src/ui.js +++ b/mascara/src/ui.js @@ -44,13 +44,18 @@ background.on('ready', (sw) => { background.removeListener('updatefound', connectApp) connectApp(sw) }) -background.on('updatefound', () => window.location.reload()) +background.on('updatefound', windowReload) background.startWorker() .then(() => { setTimeout(() => { const appContent = document.getElementById(`app-content`) - if (!appContent.children.length) window.location.reload() + if (!appContent.children.length) windowReload() }, 2000) }) console.log('hello from MetaMascara ui!') + +function windowReload() { + if (window.METAMASK_SKIP_RELOAD) return + window.location.reload() +} diff --git a/mascara/test/test-ui.js b/mascara/test/test-ui.js new file mode 100644 index 000000000..ebeffe8cb --- /dev/null +++ b/mascara/test/test-ui.js @@ -0,0 +1,13 @@ +const Helper = require('./util/mascara-test-helper.js') + +window.METAMASK_SKIP_RELOAD = true +window.addEventListener('load', () => { + // inject app container + const body = document.body + const container = document.createElement('div') + container.id = 'app-content' + body.appendChild(container) + + // start ui + require('../src/ui.js') +}) diff --git a/mascara/test/window-load.js b/mascara/test/window-load.js deleted file mode 100644 index d3f44f05f..000000000 --- a/mascara/test/window-load.js +++ /dev/null @@ -1,5 +0,0 @@ -const Helper = require('./util/mascara-test-helper.js') - -window.addEventListener('load', () => { - require('../src/ui.js') -}) diff --git a/mock-dev.js b/mock-dev.js index b6652bdf7..452fe37c5 100644 --- a/mock-dev.js +++ b/mock-dev.js @@ -96,7 +96,6 @@ function startApp(){ const container = document.createElement('div') container.id = 'app-content' body.appendChild(container) - console.log('container', container) render( h('.super-dev-container', [ diff --git a/package.json b/package.json index d618983bd..8ba6e16cb 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,7 @@ "test": "npm run lint && npm run test-unit && npm run test-integration", "test-unit": "METAMASK_ENV=test mocha --require test/helper.js --recursive \"test/unit/**/*.js\"", "single-test": "METAMASK_ENV=test mocha --require test/helper.js", - "test-integration": "npm run buildMock && npm run buildCiUnits && karma start", + "test-integration": "npm run buildMock && npm run buildCiUnits && karma start test/together.conf.js", "test-coverage": "nyc npm run test-unit && npm run coveralls-upload", "coveralls-upload": "if [ $COVERALLS_REPO_TOKEN ]; then nyc report --reporter=text-lcov | coveralls; fi", "ci": "npm run lint && npm run test-coverage && npm run test-integration", @@ -27,10 +27,11 @@ "generateNotice": "node notices/notice-generator.js", "deleteNotice": "node notices/notice-delete.js", "mascara": "node ./mascara/example/server", - "buildMascaraCi": "browserify mascara/test/window-load.js -o mascara/test/bundle.js", - "buildMascaraSWCi": "browserify mascara/src/background.js -o mascara/test/background.js", - "mascaraCi": "npm run buildMascaraCi && npm run buildMascaraSWCi && node mascara/test/index.js", - "testMascara": "cd mascara/test && npm run mascaraCi && testem ci -P 3" + "testMascara": "npm run buildMascara && karma start test/mascara.conf.js", + "buildMascara": "npm run buildMascaraUi && npm run buildMascaraBackground && npm run buildMascaraTests", + "buildMascaraUi": "browserify mascara/test/test-ui.js -o mascara/test/ui-bundle.js", + "buildMascaraBackground": "browserify mascara/src/background.js -o mascara/test/background.js", + "buildMascaraTests": "browserify test/integration/lib/first-time.js -o mascara/test/test-bundle.js" }, "browserify": { "transform": [ diff --git a/karma.conf.js b/test/base.conf.js similarity index 95% rename from karma.conf.js rename to test/base.conf.js index 8e6d55972..122392822 100644 --- a/karma.conf.js +++ b/test/base.conf.js @@ -2,7 +2,7 @@ // Generated on Mon Sep 11 2017 18:45:48 GMT-0700 (PDT) module.exports = function(config) { - config.set({ + return { // base path that will be used to resolve all patterns (eg. files, exclude) basePath: process.cwd(), @@ -16,9 +16,7 @@ module.exports = function(config) { // list of files / patterns to load in the browser files: [ - 'development/bundle.js', 'test/integration/jquery-3.1.0.min.js', - 'test/integration/bundle.js', { pattern: 'dist/chrome/images/**/*.*', watched: false, included: false, served: true }, { pattern: 'dist/chrome/fonts/**/*.*', watched: false, included: false, served: true }, ], @@ -57,5 +55,5 @@ module.exports = function(config) { // Concurrency level // how many browser should be started simultaneous concurrency: Infinity - }) + } } diff --git a/test/mascara.conf.js b/test/mascara.conf.js new file mode 100644 index 000000000..8e8fa2cdf --- /dev/null +++ b/test/mascara.conf.js @@ -0,0 +1,14 @@ +const getBaseConfig = require('./base.conf.js') + +module.exports = function(config) { + const settings = getBaseConfig(config) + settings.files.push('mascara/test/ui-bundle.js') + settings.files.push('mascara/test/test-bundle.js') + // settings.files.push('test/integration/bundle.js') + settings.files.push({ pattern: 'mascara/test/background.js', watched: false, included: false, served: true }), + // /background.js + + settings.proxies['/background.js'] = '/base/mascara/test/background.js' + + config.set(settings) +} diff --git a/test/together.conf.js b/test/together.conf.js new file mode 100644 index 000000000..cd2dbdcdc --- /dev/null +++ b/test/together.conf.js @@ -0,0 +1,8 @@ +const getBaseConfig = require('./base.conf.js') + +module.exports = function(config) { + const settings = getBaseConfig(config) + settings.files.push('development/bundle.js') + settings.files.push('test/integration/bundle.js') + config.set(settings) +} From cbff5fd450ac0a55a9ef7b48b0ce540b66dfca6b Mon Sep 17 00:00:00 2001 From: kumavis Date: Wed, 13 Sep 2017 16:52:38 -0700 Subject: [PATCH 095/140] tests - mascara - move temp build files into dist/mascara --- package.json | 8 ++++---- test/mascara.conf.js | 12 ++++++------ 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/package.json b/package.json index 8ba6e16cb..c4a19a52c 100644 --- a/package.json +++ b/package.json @@ -28,10 +28,10 @@ "deleteNotice": "node notices/notice-delete.js", "mascara": "node ./mascara/example/server", "testMascara": "npm run buildMascara && karma start test/mascara.conf.js", - "buildMascara": "npm run buildMascaraUi && npm run buildMascaraBackground && npm run buildMascaraTests", - "buildMascaraUi": "browserify mascara/test/test-ui.js -o mascara/test/ui-bundle.js", - "buildMascaraBackground": "browserify mascara/src/background.js -o mascara/test/background.js", - "buildMascaraTests": "browserify test/integration/lib/first-time.js -o mascara/test/test-bundle.js" + "buildMascara": "mkdir -p dist/mascara && npm run buildMascaraUi && npm run buildMascaraBackground && npm run buildMascaraTests", + "buildMascaraUi": "browserify mascara/test/test-ui.js -o dist/mascara/ui.js", + "buildMascaraBackground": "browserify mascara/src/background.js -o dist/mascara/background.js", + "buildMascaraTests": "browserify test/integration/lib/first-time.js -o dist/mascara/tests.js" }, "browserify": { "transform": [ diff --git a/test/mascara.conf.js b/test/mascara.conf.js index 8e8fa2cdf..b4ec5846f 100644 --- a/test/mascara.conf.js +++ b/test/mascara.conf.js @@ -2,13 +2,13 @@ const getBaseConfig = require('./base.conf.js') module.exports = function(config) { const settings = getBaseConfig(config) - settings.files.push('mascara/test/ui-bundle.js') - settings.files.push('mascara/test/test-bundle.js') - // settings.files.push('test/integration/bundle.js') - settings.files.push({ pattern: 'mascara/test/background.js', watched: false, included: false, served: true }), - // /background.js - settings.proxies['/background.js'] = '/base/mascara/test/background.js' + // ui and tests + settings.files.push('dist/mascara/ui.js') + settings.files.push('dist/mascara/tests.js') + // service worker background + settings.files.push({ pattern: 'dist/mascara/background.js', watched: false, included: false, served: true }), + settings.proxies['/background.js'] = '/base/dist/mascara/background.js' config.set(settings) } From 610f09262d8686cf975b25ffb6e2b5371e3d55c0 Mon Sep 17 00:00:00 2001 From: kumavis Date: Wed, 13 Sep 2017 17:17:09 -0700 Subject: [PATCH 096/140] tests - refactor and rename npm scripts --- circle.yml | 2 +- package.json | 47 +++++++++++++------------ test/{together.conf.js => flat.conf.js} | 0 3 files changed, 25 insertions(+), 24 deletions(-) rename test/{together.conf.js => flat.conf.js} (100%) diff --git a/circle.yml b/circle.yml index f5da6857d..6aba5c1be 100644 --- a/circle.yml +++ b/circle.yml @@ -3,7 +3,7 @@ machine: version: 8.1.4 test: override: - - "npm run ci" + - "npm test" dependencies: pre: - sudo apt-get update diff --git a/package.json b/package.json index c4a19a52c..7a022d47a 100644 --- a/package.json +++ b/package.json @@ -6,32 +6,33 @@ "scripts": { "start": "npm run dev", "dev": "gulp dev --debug", - "disc": "gulp disc --debug", - "clear": "rm -rf node_modules/eth-contract-metadata && rm -rf node_modules/eth-phishing-detect", - "dist": "npm run clear && npm install && gulp dist", - "test": "npm run lint && npm run test-unit && npm run test-integration", - "test-unit": "METAMASK_ENV=test mocha --require test/helper.js --recursive \"test/unit/**/*.js\"", - "single-test": "METAMASK_ENV=test mocha --require test/helper.js", - "test-integration": "npm run buildMock && npm run buildCiUnits && karma start test/together.conf.js", - "test-coverage": "nyc npm run test-unit && npm run coveralls-upload", - "coveralls-upload": "if [ $COVERALLS_REPO_TOKEN ]; then nyc report --reporter=text-lcov | coveralls; fi", - "ci": "npm run lint && npm run test-coverage && npm run test-integration", - "lint": "gulp lint", - "buildCiUnits": "node test/integration/index.js", - "watch": "mocha watch --recursive \"test/unit/**/*.js\"", - "genStates": "node development/genStates.js", - "ui": "npm run genStates && beefy ui-dev.js:bundle.js --live --open --index=./development/index.html --cwd ./", + "ui": "npm run test:flat:build:states && beefy ui-dev.js:bundle.js --live --open --index=./development/index.html --cwd ./", "mock": "beefy mock-dev.js:bundle.js --live --open --index=./development/index.html --cwd ./", - "buildMock": "npm run genStates && browserify ./mock-dev.js -o ./development/bundle.js", + "watch": "mocha watch --recursive \"test/unit/**/*.js\"", + "mascara": "node ./mascara/example/server", + "dist": "npm run dist:clear && npm install && gulp dist", + "dist:clear": "rm -rf node_modules/eth-contract-metadata && rm -rf node_modules/eth-phishing-detect", + "test": "npm run lint && npm run test:coverage && npm run test:integration", + "test:unit": "METAMASK_ENV=test mocha --require test/helper.js --recursive \"test/unit/**/*.js\"", + "test:single": "METAMASK_ENV=test mocha --require test/helper.js", + "test:integration": "npm run test:flat", + "test:coverage": "nyc npm run test:unit && npm run test:coveralls-upload", + "test:coveralls-upload": "if [ $COVERALLS_REPO_TOKEN ]; then nyc report --reporter=text-lcov | coveralls; fi", + "test:flat": "npm run test:flat:build && karma start test/flat.conf.js", + "test:flat:build": "npm run test:flat:build:ui && npm run test:flat:build:tests", + "test:flat:build:tests": "node test/integration/index.js", + "test:flat:build:states": "node development/genStates.js", + "test:flat:build:ui": "npm run test:flat:build:states && browserify ./mock-dev.js -o ./development/bundle.js", + "test:mascara": "npm run test:mascara:build && karma start test/mascara.conf.js", + "test:mascara:build": "mkdir -p dist/mascara && npm run test:mascara:build:ui && npm run test:mascara:build:background && npm run test:mascara:build:tests", + "test:mascara:build:ui": "browserify mascara/test/test-ui.js -o dist/mascara/ui.js", + "test:mascara:build:background": "browserify mascara/src/background.js -o dist/mascara/background.js", + "test:mascara:build:tests": "browserify test/integration/lib/first-time.js -o dist/mascara/tests.js", + "lint": "gulp lint", + "disc": "gulp disc --debug", "announce": "node development/announcer.js", "generateNotice": "node notices/notice-generator.js", - "deleteNotice": "node notices/notice-delete.js", - "mascara": "node ./mascara/example/server", - "testMascara": "npm run buildMascara && karma start test/mascara.conf.js", - "buildMascara": "mkdir -p dist/mascara && npm run buildMascaraUi && npm run buildMascaraBackground && npm run buildMascaraTests", - "buildMascaraUi": "browserify mascara/test/test-ui.js -o dist/mascara/ui.js", - "buildMascaraBackground": "browserify mascara/src/background.js -o dist/mascara/background.js", - "buildMascaraTests": "browserify test/integration/lib/first-time.js -o dist/mascara/tests.js" + "deleteNotice": "node notices/notice-delete.js" }, "browserify": { "transform": [ diff --git a/test/together.conf.js b/test/flat.conf.js similarity index 100% rename from test/together.conf.js rename to test/flat.conf.js From b53d04c40975adc0fadf20bc6ca875bd9c39e595 Mon Sep 17 00:00:00 2001 From: kumavis Date: Wed, 13 Sep 2017 20:25:27 -0700 Subject: [PATCH 097/140] tests - integration - get flat and mascara tests closer to compatible --- development/index.html | 4 +- development/test.html | 5 +- mascara/src/ui.js | 8 +- mascara/test/lib/first-time.js | 119 ----------------------------- mascara/test/test-ui.js | 1 - mock-dev.js | 4 +- test/integration/lib/first-time.js | 12 +-- test/mascara.conf.js | 3 + ui-dev.js | 4 +- 9 files changed, 18 insertions(+), 142 deletions(-) delete mode 100644 mascara/test/lib/first-time.js diff --git a/development/index.html b/development/index.html index 048aa3f35..a0814cb55 100644 --- a/development/index.html +++ b/development/index.html @@ -14,13 +14,13 @@ diff --git a/development/test.html b/development/test.html index 702be7fa0..49084c0a4 100644 --- a/development/test.html +++ b/development/test.html @@ -18,13 +18,14 @@ diff --git a/mascara/src/ui.js b/mascara/src/ui.js index 5662270c1..7506532d2 100644 --- a/mascara/src/ui.js +++ b/mascara/src/ui.js @@ -2,8 +2,6 @@ const injectCss = require('inject-css') const SWcontroller = require('client-sw-ready-event/lib/sw-client.js') const SwStream = require('sw-stream/lib/sw-stream.js') const MetaMaskUiCss = require('../../ui/css') -const setupIframe = require('./lib/setup-iframe.js') -const MetamaskInpageProvider = require('../../app/scripts/lib/inpage-provider.js') const MetamascaraPlatform = require('../../app/scripts/platforms/window') const startPopup = require('../../app/scripts/popup-core') @@ -32,7 +30,7 @@ const connectApp = function (readSw) { serviceWorker: background.controller, context: name, }) - startPopup({container, connectionStream}, (err, store) => { + startPopup({ container, connectionStream }, (err, store) => { if (err) return displayCriticalError(err) store.subscribe(() => { const state = store.getState() @@ -49,8 +47,8 @@ background.on('updatefound', windowReload) background.startWorker() .then(() => { setTimeout(() => { - const appContent = document.getElementById(`app-content`) - if (!appContent.children.length) windowReload() + const container = document.getElementById(`app-content`) + if (!container.children.length) windowReload() }, 2000) }) console.log('hello from MetaMascara ui!') diff --git a/mascara/test/lib/first-time.js b/mascara/test/lib/first-time.js deleted file mode 100644 index e42c9e39d..000000000 --- a/mascara/test/lib/first-time.js +++ /dev/null @@ -1,119 +0,0 @@ -const PASSWORD = 'password123' - -QUnit.module('first time usage') - -QUnit.test('render init screen', function (assert) { - var done = assert.async() - let app - - wait(1000).then(function() { - app = $('#app-content').contents() - const recurseNotices = function () { - let button = app.find('button') - if (button.html() === 'Accept') { - let termsPage = app.find('.markdown')[0] - termsPage.scrollTop = termsPage.scrollHeight - return wait().then(() => { - button.click() - return wait() - }).then(() => { - return recurseNotices() - }) - } else { - return wait() - } - } - return recurseNotices() - }).then(function() { - // Scroll through terms - var title = app.find('h1').text() - assert.equal(title, 'MetaMask', 'title screen') - - // enter password - var pwBox = app.find('#password-box')[0] - var confBox = app.find('#password-box-confirm')[0] - pwBox.value = PASSWORD - confBox.value = PASSWORD - - return wait() - }).then(function() { - - // create vault - var createButton = app.find('button.primary')[0] - createButton.click() - - return wait(1500) - }).then(function() { - - var created = app.find('h3')[0] - assert.equal(created.textContent, 'Vault Created', 'Vault created screen') - - // Agree button - var button = app.find('button')[0] - assert.ok(button, 'button present') - button.click() - - return wait(1000) - }).then(function() { - - var detail = app.find('.account-detail-section')[0] - assert.ok(detail, 'Account detail section loaded.') - - var sandwich = app.find('.sandwich-expando')[0] - sandwich.click() - - return wait() - }).then(function() { - - var sandwich = app.find('.menu-droppo')[0] - var children = sandwich.children - var lock = children[children.length - 2] - assert.ok(lock, 'Lock menu item found') - lock.click() - - return wait(1000) - }).then(function() { - - var pwBox = app.find('#password-box')[0] - pwBox.value = PASSWORD - - var createButton = app.find('button.primary')[0] - createButton.click() - - return wait(1000) - }).then(function() { - - var detail = app.find('.account-detail-section')[0] - assert.ok(detail, 'Account detail section loaded again.') - - return wait() - }).then(function (){ - - var qrButton = app.find('.fa.fa-qrcode')[0] - qrButton.click() - - return wait(1000) - }).then(function (){ - - var qrHeader = app.find('.qr-header')[0] - var qrContainer = app.find('#qr-container')[0] - assert.equal(qrHeader.textContent, 'Account 1', 'Should show account label.') - assert.ok(qrContainer, 'QR Container found') - - return wait() - }).then(function (){ - - var networkMenu = app.find('.network-indicator')[0] - networkMenu.click() - - return wait() - }).then(function (){ - - var networkMenu = app.find('.network-indicator')[0] - var children = networkMenu.children - children.length[3] - assert.ok(children, 'All network options present') - - done() - }) -}) diff --git a/mascara/test/test-ui.js b/mascara/test/test-ui.js index ebeffe8cb..201eec601 100644 --- a/mascara/test/test-ui.js +++ b/mascara/test/test-ui.js @@ -7,7 +7,6 @@ window.addEventListener('load', () => { const container = document.createElement('div') container.id = 'app-content' body.appendChild(container) - // start ui require('../src/ui.js') }) diff --git a/mock-dev.js b/mock-dev.js index 452fe37c5..a47f1ed4d 100644 --- a/mock-dev.js +++ b/mock-dev.js @@ -94,7 +94,7 @@ startApp() function startApp(){ const body = document.body const container = document.createElement('div') - container.id = 'app-content' + container.id = 'test-container' body.appendChild(container) render( @@ -112,7 +112,7 @@ function startApp(){ h(Selector, { actions, selectedKey: selectedView, states, store }), - h('.mock-app-root', { + h('#app-content', { style: { height: '500px', width: '360px', diff --git a/test/integration/lib/first-time.js b/test/integration/lib/first-time.js index 38a94e551..e023351bc 100644 --- a/test/integration/lib/first-time.js +++ b/test/integration/lib/first-time.js @@ -10,19 +10,11 @@ QUnit.test('render init screen', (assert) => { }) }) -// QUnit.testDone(({ module, name, total, passed, failed, skipped, todo, runtime }) => { -// if (failed > 0) { -// const app = $('iframe').contents()[0].documentElement -// console.warn('Test failures - dumping DOM:') -// console.log(app.innerHTML) -// } -// }) - async function runFirstTimeUsageTest(assert, done) { await timeout() - const app = $('#app-content .mock-app-root') + const app = $('#app-content') // recurse notices while (true) { @@ -32,10 +24,12 @@ async function runFirstTimeUsageTest(assert, done) { const termsPage = app.find('.markdown')[0] termsPage.scrollTop = termsPage.scrollHeight await timeout() + console.log('Clearing notice') button.click() await timeout() } else { // exit loop + console.log('No more notices...') break } } diff --git a/test/mascara.conf.js b/test/mascara.conf.js index b4ec5846f..97e53fc2b 100644 --- a/test/mascara.conf.js +++ b/test/mascara.conf.js @@ -10,5 +10,8 @@ module.exports = function(config) { settings.files.push({ pattern: 'dist/mascara/background.js', watched: false, included: false, served: true }), settings.proxies['/background.js'] = '/base/dist/mascara/background.js' + // use this to keep the browser open for debugging + settings.browserNoActivityTimeout = 10000000 + config.set(settings) } diff --git a/ui-dev.js b/ui-dev.js index 367b5d546..de5dfd8ef 100644 --- a/ui-dev.js +++ b/ui-dev.js @@ -61,7 +61,7 @@ const actions = { var css = MetaMaskUiCss() injectCss(css) -const container = document.querySelector('#app-content') +const container = document.querySelector('#test-container') // parse opts var store = configureStore(states[selectedView]) @@ -72,7 +72,7 @@ render( h(Selector, { actions, selectedKey: selectedView, states, store }), - h('.mock-app-root', { + h('#app-content', { style: { height: '500px', width: '360px', From 6268272c834250e9cc6eb92bc1d7d6293fe5980d Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Thu, 14 Sep 2017 13:31:10 -0700 Subject: [PATCH 098/140] Add guide to porting to new platforms Adds a new guide to porting MetaMask to new platforms. Intended for all those devs asking us how to make a mobile MetaMask. --- README.md | 1 + docs/porting_to_new_environment.md | 65 ++++++++++++++++++++++++++++++ 2 files changed, 66 insertions(+) create mode 100644 docs/porting_to_new_environment.md diff --git a/README.md b/README.md index 075db79c2..b549ade08 100644 --- a/README.md +++ b/README.md @@ -71,6 +71,7 @@ To write tests that will be run in the browser using QUnit, add your test files - [How to live reload on local dependency changes](./docs/developing-on-deps.md) - [How to add new networks to the Provider Menu](./docs/adding-new-networks.md) - [How to manage notices that appear when the app starts up](./docs/notices.md) +- [How to port MetaMask to a new platform](./docs/porting_to_new_environment.md) - [How to generate a visualization of this repository's development](./docs/development-visualization.md) [1]: http://www.nomnoml.com/#view/%5B%3Cactor%3Euser%5D%0A%0A%5Bmetamask-ui%7C%0A%20%20%20%5Btools%7C%0A%20%20%20%20%20react%0A%20%20%20%20%20redux%0A%20%20%20%20%20thunk%0A%20%20%20%20%20ethUtils%0A%20%20%20%20%20jazzicon%0A%20%20%20%5D%0A%20%20%20%5Bcomponents%7C%0A%20%20%20%20%20app%0A%20%20%20%20%20account-detail%0A%20%20%20%20%20accounts%0A%20%20%20%20%20locked-screen%0A%20%20%20%20%20restore-vault%0A%20%20%20%20%20identicon%0A%20%20%20%20%20config%0A%20%20%20%20%20info%0A%20%20%20%5D%0A%20%20%20%5Breducers%7C%0A%20%20%20%20%20app%0A%20%20%20%20%20metamask%0A%20%20%20%20%20identities%0A%20%20%20%5D%0A%20%20%20%5Bactions%7C%0A%20%20%20%20%20%5BaccountManager%5D%0A%20%20%20%5D%0A%20%20%20%5Bcomponents%5D%3A-%3E%5Bactions%5D%0A%20%20%20%5Bactions%5D%3A-%3E%5Breducers%5D%0A%20%20%20%5Breducers%5D%3A-%3E%5Bcomponents%5D%0A%5D%0A%0A%5Bweb%20dapp%7C%0A%20%20%5Bui%20code%5D%0A%20%20%5Bweb3%5D%0A%20%20%5Bmetamask-inpage%5D%0A%20%20%0A%20%20%5B%3Cactor%3Eui%20developer%5D%0A%20%20%5Bui%20developer%5D-%3E%5Bui%20code%5D%0A%20%20%5Bui%20code%5D%3C-%3E%5Bweb3%5D%0A%20%20%5Bweb3%5D%3C-%3E%5Bmetamask-inpage%5D%0A%5D%0A%0A%5Bmetamask-background%7C%0A%20%20%5Bprovider-engine%5D%0A%20%20%5Bhooked%20wallet%20subprovider%5D%0A%20%20%5Bid%20store%5D%0A%20%20%0A%20%20%5Bprovider-engine%5D%3C-%3E%5Bhooked%20wallet%20subprovider%5D%0A%20%20%5Bhooked%20wallet%20subprovider%5D%3C-%3E%5Bid%20store%5D%0A%20%20%5Bconfig%20manager%7C%0A%20%20%20%20%5Brpc%20configuration%5D%0A%20%20%20%20%5Bencrypted%20keys%5D%0A%20%20%20%20%5Bwallet%20nicknames%5D%0A%20%20%5D%0A%20%20%0A%20%20%5Bprovider-engine%5D%3C-%5Bconfig%20manager%5D%0A%20%20%5Bid%20store%5D%3C-%3E%5Bconfig%20manager%5D%0A%5D%0A%0A%5Buser%5D%3C-%3E%5Bmetamask-ui%5D%0A%0A%5Buser%5D%3C%3A--%3A%3E%5Bweb%20dapp%5D%0A%0A%5Bmetamask-contentscript%7C%0A%20%20%5Bplugin%20restart%20detector%5D%0A%20%20%5Brpc%20passthrough%5D%0A%5D%0A%0A%5Brpc%20%7C%0A%20%20%5Bethereum%20blockchain%20%7C%0A%20%20%20%20%5Bcontracts%5D%0A%20%20%20%20%5Baccounts%5D%0A%20%20%5D%0A%5D%0A%0A%5Bweb%20dapp%5D%3C%3A--%3A%3E%5Bmetamask-contentscript%5D%0A%5Bmetamask-contentscript%5D%3C-%3E%5Bmetamask-background%5D%0A%5Bmetamask-background%5D%3C-%3E%5Bmetamask-ui%5D%0A%5Bmetamask-background%5D%3C-%3E%5Brpc%5D%0A diff --git a/docs/porting_to_new_environment.md b/docs/porting_to_new_environment.md new file mode 100644 index 000000000..85670efa7 --- /dev/null +++ b/docs/porting_to_new_environment.md @@ -0,0 +1,65 @@ +# Guide to Porting MetaMask to a New Environment + +MetaMask has been under continuous development for nearly two years now, and we’ve gradually discovered some very useful abstractions, that have allowed us to grow more easily. A couple of those layers together allow MetaMask to be ported to new environments and contexts increasingly easily. + +### The MetaMask Controller + +The core functionality of MetaMask all lives in what we call [The MetaMask Controller](https://github.com/MetaMask/metamask-extension/blob/master/app/scripts/metamask-controller.js). Our goal for this file is for it to eventually be its own javascript module that can be imported into any JS-compatible context, allowing it to fully manage an app's relationship to Ethereum. + +The MM Controller exposes most of its functionality via two methods: + +- [metamask.getState()](https://github.com/MetaMask/metamask-extension/blob/master/app/scripts/metamask-controller.js#L241) - This method returns a javascript object representing the current MetaMask state. This includes things like known accounts, sent transactions, current exchange rates, and more! The controller is also an event emitter, so you can subscribe to state updates via `metamask.on('update', handleStateUpdate)`. State examples available [here](https://github.com/MetaMask/metamask-extension/tree/master/development/states) under the `metamask` key. (Warning: some are outdated) +- [metamask.getApi()](https://github.com/MetaMask/metamask-extension/blob/master/app/scripts/metamask-controller.js#L274-L335) - Returns a JavaScript object filled with callback functions representing every operation our user interface ever performs. Everything from creating new accounts, changing the current network, to sending a transaction, is provided via these API methods. We export this external API on an object because it allows us to easily expose this API over a port using [dnode](https://www.npmjs.com/package/dnode), which is how our WebExtension's UI works! + +### The UI + +The MetaMask UI is essentially just a website that can be configured by passing it the API and state subscriptions from above. Anyone could make a UI that consumes these, effectively reskinning MetaMask. + +You can see this in action in our file [ui/index.js](https://github.com/MetaMask/metamask-extension/blob/master/ui/index.js). There you can see an argument being passed in named `accountManager`, which is essentially a MetaMask controller (forgive its really outdated parameter name!). With access to that object, the UI is able to initialize a whole React/Redux app that relies on this API for its account/blockchain-related/persistent states. + +## Putting it Together + +As an example, a WebExtension is always defined by a `manifest.json` file. [In ours](https://github.com/MetaMask/metamask-extension/blob/master/app/manifest.json#L31), you can see that [background.js](https://github.com/MetaMask/metamask-extension/blob/master/app/scripts/background.js) is defined as a script to run in the background, and this is the file that we use to initialize the MetaMask controller. + +In that file, there's a lot going on, so it's maybe worth focusing on our MetaMask controller constructor to start. It looks something like this: + +```javascript +const controller = new MetamaskController({ + // User confirmation callbacks: + showUnconfirmedMessage: triggerUi, + unlockAccountMessage: triggerUi, + showUnapprovedTx: triggerUi, + // initial state + initState, + // platform specific api + platform, +}) +``` +Since `background.js` is essentially the Extension setup file, we can see it doing all the things specific to the extension platform: +- Defining how to open the UI for new messages, transactions, and even requests to unlock (reveal to the site) their account. +- Provide the instance's initial state, leaving MetaMask persistence to the platform. +- Providing a `platform` object. This is becoming our catch-all adapter for platforms to define a few other platform-variant features we require, like opening a web link. (Soon we will be moving encryption out here too, since our browser-encryption isn't portable enough!) + +## Ports, streams, and Web3! + +Everything so far has been enough to create a MetaMask wallet on virtually any platform that runs JS, but MetaMask's most unique feature isn't being a wallet, it's providing an Ethereum-enabled JavaScript context to websites. + +MetaMask has two kinds of [duplex stream APIs](https://github.com/substack/stream-handbook#duplex) that it exposes: +- [metamask.setupTrustedCommunication(connectionStream, originDomain)](https://github.com/MetaMask/metamask-extension/blob/master/app/scripts/metamask-controller.js#L352) - This stream is used to connect the user interface over a remote port, and may not be necessary for contexts where the interface and the metamask-controller share a process. +- [metamask.setupUntrustedCommunication(connectionStream, originDomain)](https://github.com/MetaMask/metamask-extension/blob/master/app/scripts/metamask-controller.js#L337) - This method is used to connect a new web site's web3 API to MetaMask's blockchain connection. Additionally, the `originDomain` is used to block detected phishing sites. + +### Web3 as a Stream + +If you are making a MetaMask-powered browser for a new platform, one of the trickiest tasks will be injecting the Web3 API into websites that are visited. On WebExtensions, we actually have to pipe data through a total of three JS contexts just to let sites talk to our background process (site -> contentscript -> background). + +To make this as easy as possible, we use one of our favorite internal tools, [web3-provider-engine](https://www.npmjs.com/package/web3-provider-engine) to construct a custom web3 provider object whose source of truth is a stream that we connect to remotely. + +To see how we do that, you can refer to the [inpage script](https://github.com/MetaMask/metamask-extension/blob/master/app/scripts/inpage.js) that we inject into every website. There you can see it creates a multiplex stream to the background, and uses it to initialize what we call the [inpage-provider](https://github.com/MetaMask/metamask-extension/blob/master/app/scripts/lib/inpage-provider.js), which you can see stubs a few methods out, but mostly just passes calls to `sendAsync` through the stream it's passed! That's really all the magic that's needed to create a web3-like API in a remote context, once you have a stream to MetaMask available. + +In `inpage.js` you can see we create a `PortStream`, that's just a class we use to wrap WebExtension ports as streams, so we can reuse our favorite stream abstraction over the more irregular API surface of the WebExtension. In a new platform, you will probably need to construct this stream differently. The key is that you need to construct a stream that talks from the site context to the background. Once you have that set up, it works like magic! + +If streams seem new and confusing to you, that's ok, they can seem strange at first. To help learn them, we highly recommend reading Substack's [Stream Handbook](https://github.com/substack/stream-handbook), or going through NodeSchool's interactive command-line class [Stream Adventure](https://github.com/workshopper/stream-adventure), also maintained by Substack. + +## Conclusion + +I hope this has been helpful to you! If you have any other questionsm, or points you think need clarification in this guide, please [open an issue on our GitHub](https://github.com/MetaMask/metamask-plugin/issues/new)! \ No newline at end of file From 7fb862356873fc4c5dc28e5c7389f71424985b0f Mon Sep 17 00:00:00 2001 From: frankiebee Date: Fri, 15 Sep 2017 11:09:19 -0700 Subject: [PATCH 099/140] dont reload on initial download and wait before passing sw to ui --- mascara/src/ui.js | 41 +++++++++++++++++++----------- mascara/test/test-ui.js | 1 - test/integration/lib/first-time.js | 4 +-- 3 files changed, 28 insertions(+), 18 deletions(-) diff --git a/mascara/src/ui.js b/mascara/src/ui.js index 7506532d2..05521d095 100644 --- a/mascara/src/ui.js +++ b/mascara/src/ui.js @@ -30,30 +30,41 @@ const connectApp = function (readSw) { serviceWorker: background.controller, context: name, }) - startPopup({ container, connectionStream }, (err, store) => { - if (err) return displayCriticalError(err) - store.subscribe(() => { - const state = store.getState() - if (state.appState.shouldClose) window.close() + return new Promise((resolve, reject) => { + startPopup({ container, connectionStream }, (err, store) => { + console.log('hello from MetaMascara ui!') + if (err) reject(err) + store.subscribe(() => { + const state = store.getState() + if (state.appState.shouldClose) window.close() + }) + resolve() }) }) } -background.on('ready', (sw) => { - background.removeListener('updatefound', connectApp) - connectApp(sw) +background.on('ready', async (sw) => { + try { + background.removeListener('updatefound', connectApp) + await timeout(1000) + await connectApp(sw) + console.log('hello from cb ready event!') + } catch (e) { + console.error(e) + } }) background.on('updatefound', windowReload) background.startWorker() -.then(() => { - setTimeout(() => { - const container = document.getElementById(`app-content`) - if (!container.children.length) windowReload() - }, 2000) -}) -console.log('hello from MetaMascara ui!') function windowReload() { if (window.METAMASK_SKIP_RELOAD) return window.location.reload() } + +function timeout (time) { + return new Promise(function (resolve, reject) { + setTimeout(function () { + resolve() + }, time || 1500) + }) +} \ No newline at end of file diff --git a/mascara/test/test-ui.js b/mascara/test/test-ui.js index 201eec601..bf27338d9 100644 --- a/mascara/test/test-ui.js +++ b/mascara/test/test-ui.js @@ -1,6 +1,5 @@ const Helper = require('./util/mascara-test-helper.js') -window.METAMASK_SKIP_RELOAD = true window.addEventListener('load', () => { // inject app container const body = document.body diff --git a/test/integration/lib/first-time.js b/test/integration/lib/first-time.js index e023351bc..4140dfd78 100644 --- a/test/integration/lib/first-time.js +++ b/test/integration/lib/first-time.js @@ -12,7 +12,7 @@ QUnit.test('render init screen', (assert) => { async function runFirstTimeUsageTest(assert, done) { - await timeout() + await timeout(10000) const app = $('#app-content') @@ -123,7 +123,7 @@ async function runFirstTimeUsageTest(assert, done) { assert.ok(children2, 'All network options present') } -function timeout(time) { +function timeout (time) { return new Promise(function (resolve, reject) { setTimeout(function () { resolve() From 7bcca782cbea545fedabf33d73311c0e361690c2 Mon Sep 17 00:00:00 2001 From: frankiebee Date: Fri, 15 Sep 2017 11:10:52 -0700 Subject: [PATCH 100/140] remove testem files --- mascara/test/index.html | 21 --------------------- mascara/test/testem.yml | 13 ------------- test/integration/lib/first-time.js | 2 +- 3 files changed, 1 insertion(+), 35 deletions(-) delete mode 100644 mascara/test/index.html delete mode 100644 mascara/test/testem.yml diff --git a/mascara/test/index.html b/mascara/test/index.html deleted file mode 100644 index 6495c2cfc..000000000 --- a/mascara/test/index.html +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - QUnit Example - - - -
-
- - - - - - -
- - - diff --git a/mascara/test/testem.yml b/mascara/test/testem.yml deleted file mode 100644 index f1f5844bd..000000000 --- a/mascara/test/testem.yml +++ /dev/null @@ -1,13 +0,0 @@ -launch_in_dev: - - Chrome - - Firefox - - Opera -launch_in_ci: - - Chrome - - Firefox - - Opera -framework: - - qunit -before_tests: "npm run mascaraCi" -after_tests: "rm ./background.js ./test-bundle.js ./bundle.js" -test_page: "./index.html" diff --git a/test/integration/lib/first-time.js b/test/integration/lib/first-time.js index 4140dfd78..3f0fe165b 100644 --- a/test/integration/lib/first-time.js +++ b/test/integration/lib/first-time.js @@ -12,7 +12,7 @@ QUnit.test('render init screen', (assert) => { async function runFirstTimeUsageTest(assert, done) { - await timeout(10000) + await timeout() const app = $('#app-content') From c5a2527c17f9c7f47645dc4d99694645d1985022 Mon Sep 17 00:00:00 2001 From: frankiebee Date: Fri, 15 Sep 2017 11:33:37 -0700 Subject: [PATCH 101/140] set time if platform is mascara --- mascara/src/ui.js | 1 + test/integration/lib/first-time.js | 6 ++++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/mascara/src/ui.js b/mascara/src/ui.js index 05521d095..d70dca724 100644 --- a/mascara/src/ui.js +++ b/mascara/src/ui.js @@ -15,6 +15,7 @@ const container = document.getElementById('app-content') var name = 'popup' window.METAMASK_UI_TYPE = name +window.METAMASK_PLATFORM_TYPE = 'mascara' let intervalDelay = Math.floor(Math.random() * (30000 - 1000)) + 1000 diff --git a/test/integration/lib/first-time.js b/test/integration/lib/first-time.js index 3f0fe165b..e84a51c64 100644 --- a/test/integration/lib/first-time.js +++ b/test/integration/lib/first-time.js @@ -11,8 +11,10 @@ QUnit.test('render init screen', (assert) => { }) async function runFirstTimeUsageTest(assert, done) { - - await timeout() + let waitTime = 0 + window.METAMASK_SKIP_RELOAD = true + if (window.METAMASK_PLATFORM_TYPE === 'mascara') waitTime = 1000 + await timeout(waitTime) const app = $('#app-content') From 779e973b45ac3c5c48d471218ed63848a6653a56 Mon Sep 17 00:00:00 2001 From: kumavis Date: Fri, 15 Sep 2017 12:19:52 -0700 Subject: [PATCH 102/140] tests - integration - mascara - small cleanup and timeout adjustments --- mascara/src/ui.js | 6 ++---- test/integration/lib/first-time.js | 8 +++----- 2 files changed, 5 insertions(+), 9 deletions(-) diff --git a/mascara/src/ui.js b/mascara/src/ui.js index d70dca724..2f940ad1a 100644 --- a/mascara/src/ui.js +++ b/mascara/src/ui.js @@ -63,9 +63,7 @@ function windowReload() { } function timeout (time) { - return new Promise(function (resolve, reject) { - setTimeout(function () { - resolve() - }, time || 1500) + return new Promise((resolve) => { + setTimeout(resolve, time || 1500) }) } \ No newline at end of file diff --git a/test/integration/lib/first-time.js b/test/integration/lib/first-time.js index e84a51c64..8b3997867 100644 --- a/test/integration/lib/first-time.js +++ b/test/integration/lib/first-time.js @@ -13,7 +13,7 @@ QUnit.test('render init screen', (assert) => { async function runFirstTimeUsageTest(assert, done) { let waitTime = 0 window.METAMASK_SKIP_RELOAD = true - if (window.METAMASK_PLATFORM_TYPE === 'mascara') waitTime = 1000 + if (window.METAMASK_PLATFORM_TYPE === 'mascara') waitTime = 2000 await timeout(waitTime) const app = $('#app-content') @@ -126,9 +126,7 @@ async function runFirstTimeUsageTest(assert, done) { } function timeout (time) { - return new Promise(function (resolve, reject) { - setTimeout(function () { - resolve() - }, time * 3 || 1500) + return new Promise((resolve, reject) => { + setTimeout(resolve, time || 1500) }) } \ No newline at end of file From d2d6f6a858a1ffa1871830f0fbc39ecc76935321 Mon Sep 17 00:00:00 2001 From: kumavis Date: Fri, 15 Sep 2017 12:20:43 -0700 Subject: [PATCH 103/140] tests - integration - add mascara to integration test run --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 7a022d47a..4bb410a8d 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,7 @@ "test": "npm run lint && npm run test:coverage && npm run test:integration", "test:unit": "METAMASK_ENV=test mocha --require test/helper.js --recursive \"test/unit/**/*.js\"", "test:single": "METAMASK_ENV=test mocha --require test/helper.js", - "test:integration": "npm run test:flat", + "test:integration": "npm run test:flat && npm run test:mascara", "test:coverage": "nyc npm run test:unit && npm run test:coveralls-upload", "test:coveralls-upload": "if [ $COVERALLS_REPO_TOKEN ]; then nyc report --reporter=text-lcov | coveralls; fi", "test:flat": "npm run test:flat:build && karma start test/flat.conf.js", From 0ab37b52c0540e8877321b40fc742928e96f7ee3 Mon Sep 17 00:00:00 2001 From: kumavis Date: Fri, 15 Sep 2017 13:13:13 -0700 Subject: [PATCH 104/140] mascara - proxy - small cleanup --- mascara/src/proxy.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/mascara/src/proxy.js b/mascara/src/proxy.js index 5b95175f1..07c5b0e3c 100644 --- a/mascara/src/proxy.js +++ b/mascara/src/proxy.js @@ -1,7 +1,6 @@ const createParentStream = require('iframe-stream').ParentStream const SWcontroller = require('client-sw-ready-event/lib/sw-client.js') const SwStream = require('sw-stream/lib/sw-stream.js') -const SetupUntrustedComunication = ('./lib/setup-untrusted-connection.js') let intervalDelay = Math.floor(Math.random() * (30000 - 1000)) + 1000 const background = new SWcontroller({ @@ -12,7 +11,7 @@ const background = new SWcontroller({ }) const pageStream = createParentStream() -background.on('ready', (_) => { +background.on('ready', () => { let swStream = SwStream({ serviceWorker: background.controller, context: 'dapp', From a9900be08562c16b2f77e41f18cb80076ded09ec Mon Sep 17 00:00:00 2001 From: kumavis Date: Fri, 15 Sep 2017 13:13:53 -0700 Subject: [PATCH 105/140] test - mascara - move skip reload test flag --- mascara/test/test-ui.js | 1 + test/integration/lib/first-time.js | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/mascara/test/test-ui.js b/mascara/test/test-ui.js index bf27338d9..b9bc42dff 100644 --- a/mascara/test/test-ui.js +++ b/mascara/test/test-ui.js @@ -1,6 +1,7 @@ const Helper = require('./util/mascara-test-helper.js') window.addEventListener('load', () => { + window.METAMASK_SKIP_RELOAD = true // inject app container const body = document.body const container = document.createElement('div') diff --git a/test/integration/lib/first-time.js b/test/integration/lib/first-time.js index 8b3997867..c72abf81d 100644 --- a/test/integration/lib/first-time.js +++ b/test/integration/lib/first-time.js @@ -12,7 +12,6 @@ QUnit.test('render init screen', (assert) => { async function runFirstTimeUsageTest(assert, done) { let waitTime = 0 - window.METAMASK_SKIP_RELOAD = true if (window.METAMASK_PLATFORM_TYPE === 'mascara') waitTime = 2000 await timeout(waitTime) From 5d01ca10e46953999bf9e626b6aab4b99f27a4c4 Mon Sep 17 00:00:00 2001 From: kumavis Date: Fri, 15 Sep 2017 13:19:31 -0700 Subject: [PATCH 106/140] tests - mascara - increase timeout before test starts --- test/integration/lib/first-time.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/integration/lib/first-time.js b/test/integration/lib/first-time.js index c72abf81d..104f22772 100644 --- a/test/integration/lib/first-time.js +++ b/test/integration/lib/first-time.js @@ -12,7 +12,7 @@ QUnit.test('render init screen', (assert) => { async function runFirstTimeUsageTest(assert, done) { let waitTime = 0 - if (window.METAMASK_PLATFORM_TYPE === 'mascara') waitTime = 2000 + if (window.METAMASK_PLATFORM_TYPE === 'mascara') waitTime = 4000 await timeout(waitTime) const app = $('#app-content') From c90c904f95aa725ebd48c6f90939c9d9cba55d47 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Mon, 18 Sep 2017 11:47:59 -0700 Subject: [PATCH 107/140] Version 3.10.1 --- CHANGELOG.md | 2 ++ app/manifest.json | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ce2327c24..5ef0ce17a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Current Master +## 3.10.1 2017-9-18 + - Add ability to export private keys as a file. - Add ability to export seed words as a file. - Changed state logs to a file download than a clipboard copy. diff --git a/app/manifest.json b/app/manifest.json index bd25c1f6f..8febf91aa 100644 --- a/app/manifest.json +++ b/app/manifest.json @@ -1,7 +1,7 @@ { "name": "MetaMask", "short_name": "Metamask", - "version": "3.10.0", + "version": "3.10.1", "manifest_version": 2, "author": "https://metamask.io", "description": "Ethereum Browser Extension", From 784510f89fcb460bcf77ba477fe9a7c144c25f63 Mon Sep 17 00:00:00 2001 From: kumavis Date: Mon, 18 Sep 2017 12:31:44 -0700 Subject: [PATCH 108/140] tests - integration - remove failing mascara integration tests from normal run until fixed --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 4bb410a8d..7a022d47a 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,7 @@ "test": "npm run lint && npm run test:coverage && npm run test:integration", "test:unit": "METAMASK_ENV=test mocha --require test/helper.js --recursive \"test/unit/**/*.js\"", "test:single": "METAMASK_ENV=test mocha --require test/helper.js", - "test:integration": "npm run test:flat && npm run test:mascara", + "test:integration": "npm run test:flat", "test:coverage": "nyc npm run test:unit && npm run test:coveralls-upload", "test:coveralls-upload": "if [ $COVERALLS_REPO_TOKEN ]; then nyc report --reporter=text-lcov | coveralls; fi", "test:flat": "npm run test:flat:build && karma start test/flat.conf.js", From 9f665d8eda6d290511d2ee8ca0121d9a8b8c892b Mon Sep 17 00:00:00 2001 From: kumavis Date: Mon, 18 Sep 2017 13:06:35 -0700 Subject: [PATCH 109/140] test - integration - bump timeout --- test/integration/lib/first-time.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/integration/lib/first-time.js b/test/integration/lib/first-time.js index 104f22772..cedb14f6e 100644 --- a/test/integration/lib/first-time.js +++ b/test/integration/lib/first-time.js @@ -53,7 +53,7 @@ async function runFirstTimeUsageTest(assert, done) { const createButton = app.find('button.primary')[0] createButton.click() - await timeout(1500) + await timeout(3000) const created = app.find('h3')[0] assert.equal(created.textContent, 'Vault Created', 'Vault created screen') From eaa85f283df55053024f9e561e3a816f033a7755 Mon Sep 17 00:00:00 2001 From: kumavis Date: Mon, 18 Sep 2017 13:17:08 -0700 Subject: [PATCH 110/140] tests - integration - re-add mascara to normal run --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 7a022d47a..4bb410a8d 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,7 @@ "test": "npm run lint && npm run test:coverage && npm run test:integration", "test:unit": "METAMASK_ENV=test mocha --require test/helper.js --recursive \"test/unit/**/*.js\"", "test:single": "METAMASK_ENV=test mocha --require test/helper.js", - "test:integration": "npm run test:flat", + "test:integration": "npm run test:flat && npm run test:mascara", "test:coverage": "nyc npm run test:unit && npm run test:coveralls-upload", "test:coveralls-upload": "if [ $COVERALLS_REPO_TOKEN ]; then nyc report --reporter=text-lcov | coveralls; fi", "test:flat": "npm run test:flat:build && karma start test/flat.conf.js", From 2b7b1db851e484cda68352a365215d49fa1c30aa Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Mon, 18 Sep 2017 14:34:25 -0700 Subject: [PATCH 111/140] Do not mark a retry tx failed that has been broadcast successfully Fixes #2115 If a tx has been braodcast, the only failures we should accept are: - Never mined - On chain failure We had a section of code that would mark a tx failed during any unknown error during a retry. Now no retry > 1 will ever mark a tx failed, since it has been broadcast, and may be mined. --- app/scripts/lib/pending-tx-tracker.js | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/app/scripts/lib/pending-tx-tracker.js b/app/scripts/lib/pending-tx-tracker.js index b90851b58..44e9d50fa 100644 --- a/app/scripts/lib/pending-tx-tracker.js +++ b/app/scripts/lib/pending-tx-tracker.js @@ -76,6 +76,9 @@ module.exports = class PendingTransactionTracker extends EventEmitter { 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" + + Also don't mark as failed if it has ever been broadcast successfully. + A successful broadcast means it may still be mined. */ const errorMessage = err.message.toLowerCase() const isKnownTx = ( @@ -88,6 +91,7 @@ module.exports = class PendingTransactionTracker extends EventEmitter { // other || errorMessage.includes('gateway timeout') || errorMessage.includes('nonce too low') + || txMeta.retryCount > 1 ) // ignore resubmit warnings, return early if (isKnownTx) return @@ -117,10 +121,12 @@ module.exports = class PendingTransactionTracker extends EventEmitter { // 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) + const txHash = await this.publishTransaction(rawTx) + + // Increment successful tries: + txMeta.retryCount++ + return txHash } async _checkPendingTx (txMeta) { From 92e738d17c71d43487b4037df766453d6d8c9273 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Mon, 18 Sep 2017 14:37:28 -0700 Subject: [PATCH 112/140] Bump changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5ef0ce17a..464cbe43c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Current Master +- Fix bug that would sometimes display transactions as failed that could be successfully mined. + ## 3.10.1 2017-9-18 - Add ability to export private keys as a file. From 51b40adecd5586e6ede75362fcfc4756a8ec0062 Mon Sep 17 00:00:00 2001 From: kumavis Date: Mon, 18 Sep 2017 22:42:04 -0700 Subject: [PATCH 113/140] v3.10.2 published `v3.10.2` as an emergency rollback to `v3.10.0` --- app/manifest.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/manifest.json b/app/manifest.json index 8febf91aa..67fb543b9 100644 --- a/app/manifest.json +++ b/app/manifest.json @@ -1,7 +1,7 @@ { "name": "MetaMask", "short_name": "Metamask", - "version": "3.10.1", + "version": "3.10.2", "manifest_version": 2, "author": "https://metamask.io", "description": "Ethereum Browser Extension", From 0424ab3e48596ec682cc58992879da377dc9dc55 Mon Sep 17 00:00:00 2001 From: kumavis Date: Mon, 18 Sep 2017 22:44:40 -0700 Subject: [PATCH 114/140] v3.10.2 - changelog add changelog --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 464cbe43c..c4366db45 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,10 @@ - Fix bug that would sometimes display transactions as failed that could be successfully mined. +## 3.10.2 2017-9-18 + +rollback to 3.10.0 due to bug + ## 3.10.1 2017-9-18 - Add ability to export private keys as a file. From d2ded61cc9ae9a9354d947c24929f74e8139a2bc Mon Sep 17 00:00:00 2001 From: kumavis Date: Tue, 19 Sep 2017 10:54:41 -0700 Subject: [PATCH 115/140] deps - bump json-rpc-engine --- package.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/package.json b/package.json index 3f9d9c538..986e6e187 100644 --- a/package.json +++ b/package.json @@ -93,7 +93,7 @@ "iframe-stream": "^3.0.0", "inject-css": "^0.1.1", "jazzicon": "^1.2.0", - "json-rpc-engine": "^3.1.0", + "json-rpc-engine": "^3.2.0", "json-rpc-middleware-stream": "^1.0.0", "loglevel": "^1.4.1", "metamask-logo": "^2.1.2", @@ -174,7 +174,6 @@ "jsdom": "^11.1.0", "jsdom-global": "^3.0.2", "jshint-stylish": "~2.2.1", - "json-rpc-engine": "^3.0.1", "karma": "^1.7.1", "karma-chrome-launcher": "^2.2.0", "karma-cli": "^1.0.1", From b979c6a2f3856525faaff0de94a3e97e322d18b6 Mon Sep 17 00:00:00 2001 From: kumavis Date: Tue, 19 Sep 2017 11:22:55 -0700 Subject: [PATCH 116/140] deps - bump json-rpc-middleware-stream --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 986e6e187..7d5f01f2d 100644 --- a/package.json +++ b/package.json @@ -94,7 +94,7 @@ "inject-css": "^0.1.1", "jazzicon": "^1.2.0", "json-rpc-engine": "^3.2.0", - "json-rpc-middleware-stream": "^1.0.0", + "json-rpc-middleware-stream": "^1.0.1", "loglevel": "^1.4.1", "metamask-logo": "^2.1.2", "mississippi": "^1.2.0", From 3a3e1511e5cbeab1b94bb4052050c67eb3635ecc Mon Sep 17 00:00:00 2001 From: kumavis Date: Tue, 19 Sep 2017 11:30:06 -0700 Subject: [PATCH 117/140] changelog - add note on filter fixes --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c4366db45..3ff062cf8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ## Current Master +- Fix bug where metamask-dapp connections are lost on rpc error - Fix bug that would sometimes display transactions as failed that could be successfully mined. ## 3.10.2 2017-9-18 From 8a874824a8e8dc5e16289f7753b13f96497379cd Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Thu, 21 Sep 2017 11:45:33 -0700 Subject: [PATCH 118/140] Version 3.10.3 --- CHANGELOG.md | 2 ++ app/manifest.json | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3ff062cf8..e692c58dc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Current Master +## 3.10.3 2017-9-21 + - Fix bug where metamask-dapp connections are lost on rpc error - Fix bug that would sometimes display transactions as failed that could be successfully mined. diff --git a/app/manifest.json b/app/manifest.json index 67fb543b9..fd07f15a9 100644 --- a/app/manifest.json +++ b/app/manifest.json @@ -1,7 +1,7 @@ { "name": "MetaMask", "short_name": "Metamask", - "version": "3.10.2", + "version": "3.10.3", "manifest_version": 2, "author": "https://metamask.io", "description": "Ethereum Browser Extension", From e9043f22dfa7856e3360b312ce480e71f36d9381 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Thu, 21 Sep 2017 15:47:25 -0700 Subject: [PATCH 119/140] Allow custom encryptor to be passed to MetaMaskController and KeyringControllers. --- app/scripts/keyring-controller.js | 2 +- app/scripts/metamask-controller.js | 3 ++- test/unit/keyring-controller-test.js | 5 +---- 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/app/scripts/keyring-controller.js b/app/scripts/keyring-controller.js index fd57fac70..adfa4a813 100644 --- a/app/scripts/keyring-controller.js +++ b/app/scripts/keyring-controller.js @@ -36,7 +36,7 @@ class KeyringController extends EventEmitter { identities: {}, }) this.ethStore = opts.ethStore - this.encryptor = encryptor + this.encryptor = opts.encryptor || encryptor this.keyrings = [] this.getNetwork = opts.getNetwork } diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index fef16c3a9..42248827f 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -95,6 +95,7 @@ module.exports = class MetamaskController extends EventEmitter { initState: initState.KeyringController, ethStore: this.ethStore, getNetwork: this.networkController.getNetworkState.bind(this.networkController), + encryptor: opts.encryptor || undefined, }) this.keyringController.on('newAccount', (address) => { this.preferencesController.setSelectedAddress(address) @@ -674,4 +675,4 @@ module.exports = class MetamaskController extends EventEmitter { return Promise.resolve(rpcTarget) }) } -} \ No newline at end of file +} diff --git a/test/unit/keyring-controller-test.js b/test/unit/keyring-controller-test.js index 2d9a53723..8d0d75f12 100644 --- a/test/unit/keyring-controller-test.js +++ b/test/unit/keyring-controller-test.js @@ -27,12 +27,9 @@ describe('KeyringController', function () { ethStore: { addAccount (acct) { accounts.push(ethUtil.addHexPrefix(acct)) }, }, + encryptor: mockEncryptor, }) - // Stub out the browser crypto for a mock encryptor. - // Browser crypto is tested in the integration test suite. - keyringController.encryptor = mockEncryptor - keyringController.createNewVaultAndKeychain(password) .then(function (newState) { newState From b25d4d5cfbf9a49e2669188198abd7377e697206 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Thu, 21 Sep 2017 15:56:44 -0700 Subject: [PATCH 120/140] Add platform docs including encryptor param --- docs/porting_to_new_environment.md | 29 +++++++++++++++++++++++++---- 1 file changed, 25 insertions(+), 4 deletions(-) diff --git a/docs/porting_to_new_environment.md b/docs/porting_to_new_environment.md index 85670efa7..c6336b9f9 100644 --- a/docs/porting_to_new_environment.md +++ b/docs/porting_to_new_environment.md @@ -6,10 +6,31 @@ MetaMask has been under continuous development for nearly two years now, and we The core functionality of MetaMask all lives in what we call [The MetaMask Controller](https://github.com/MetaMask/metamask-extension/blob/master/app/scripts/metamask-controller.js). Our goal for this file is for it to eventually be its own javascript module that can be imported into any JS-compatible context, allowing it to fully manage an app's relationship to Ethereum. -The MM Controller exposes most of its functionality via two methods: +#### Constructor -- [metamask.getState()](https://github.com/MetaMask/metamask-extension/blob/master/app/scripts/metamask-controller.js#L241) - This method returns a javascript object representing the current MetaMask state. This includes things like known accounts, sent transactions, current exchange rates, and more! The controller is also an event emitter, so you can subscribe to state updates via `metamask.on('update', handleStateUpdate)`. State examples available [here](https://github.com/MetaMask/metamask-extension/tree/master/development/states) under the `metamask` key. (Warning: some are outdated) -- [metamask.getApi()](https://github.com/MetaMask/metamask-extension/blob/master/app/scripts/metamask-controller.js#L274-L335) - Returns a JavaScript object filled with callback functions representing every operation our user interface ever performs. Everything from creating new accounts, changing the current network, to sending a transaction, is provided via these API methods. We export this external API on an object because it allows us to easily expose this API over a port using [dnode](https://www.npmjs.com/package/dnode), which is how our WebExtension's UI works! +When calling `new MetaMask(opts)`, many platform-specific options are configured. The keys on `opts` are as follows: + +- initState: The last emitted state, used for restoring persistent state between sessions. +- platform: The `platform` object defines a variety of platform-specific functions, including opening the confirmation view, and customizing the encryption method. + +##### Platform Options + +The `platform` object has a variety of options: + +- reload (function) - Will be called when MetaMask would like to reload its own context. +- openWindow ({ url }) - Will be called when MetaMask would like to open a web page. It will be passed a single `options` object with a `url` key, with a string value. +- getVersion() - Should return the current MetaMask version, as described in the current `CHANGELOG.md` or `app/manifest.json`. +- encryptor - An object that includes two methods: + - encrypt(password, object) - returns a Promise of a string that is ready for storage. + - decrypt(password, encryptedString) - Accepts the encrypted output of `encrypt` and returns a Promise of a restored `object` as it was encrypted. + +#### [metamask.getState()](https://github.com/MetaMask/metamask-extension/blob/master/app/scripts/metamask-controller.js#L241) + +This method returns a javascript object representing the current MetaMask state. This includes things like known accounts, sent transactions, current exchange rates, and more! The controller is also an event emitter, so you can subscribe to state updates via `metamask.on('update', handleStateUpdate)`. State examples available [here](https://github.com/MetaMask/metamask-extension/tree/master/development/states) under the `metamask` key. (Warning: some are outdated) + +#### [metamask.getApi()](https://github.com/MetaMask/metamask-extension/blob/master/app/scripts/metamask-controller.js#L274-L335) + +Returns a JavaScript object filled with callback functions representing every operation our user interface ever performs. Everything from creating new accounts, changing the current network, to sending a transaction, is provided via these API methods. We export this external API on an object because it allows us to easily expose this API over a port using [dnode](https://www.npmjs.com/package/dnode), which is how our WebExtension's UI works! ### The UI @@ -62,4 +83,4 @@ If streams seem new and confusing to you, that's ok, they can seem strange at fi ## Conclusion -I hope this has been helpful to you! If you have any other questionsm, or points you think need clarification in this guide, please [open an issue on our GitHub](https://github.com/MetaMask/metamask-plugin/issues/new)! \ No newline at end of file +I hope this has been helpful to you! If you have any other questionsm, or points you think need clarification in this guide, please [open an issue on our GitHub](https://github.com/MetaMask/metamask-plugin/issues/new)! From e9b7fd901862f57a92614b19f7f2caa969d4a282 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Fri, 22 Sep 2017 10:41:14 -0700 Subject: [PATCH 121/140] Patch security update --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 0d02f1df5..bcfb6c1ac 100644 --- a/package.json +++ b/package.json @@ -129,7 +129,7 @@ "redux": "^3.0.5", "redux-logger": "^3.0.6", "redux-thunk": "^2.2.0", - "request-promise": "^4.1.1", + "request-promise": "^4.2.1", "sandwich-expando": "^1.0.5", "semaphore": "^1.0.5", "sw-stream": "^2.0.0", From 4c971ebfd1fad18368ec418c36c4d05a6bb37e6d Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Fri, 22 Sep 2017 13:25:08 -0700 Subject: [PATCH 122/140] Define encryptor in constructor params instead of platform object --- app/scripts/metamask-controller.js | 2 +- docs/porting_to_new_environment.md | 14 ++++++++++---- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index e4fa3518f..42248827f 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -95,7 +95,7 @@ module.exports = class MetamaskController extends EventEmitter { initState: initState.KeyringController, ethStore: this.ethStore, getNetwork: this.networkController.getNetworkState.bind(this.networkController), - encryptor: opts.platform.encryptor || undefined, + encryptor: opts.encryptor || undefined, }) this.keyringController.on('newAccount', (address) => { this.preferencesController.setSelectedAddress(address) diff --git a/docs/porting_to_new_environment.md b/docs/porting_to_new_environment.md index c6336b9f9..729a28e5d 100644 --- a/docs/porting_to_new_environment.md +++ b/docs/porting_to_new_environment.md @@ -11,7 +11,16 @@ The core functionality of MetaMask all lives in what we call [The MetaMask Contr When calling `new MetaMask(opts)`, many platform-specific options are configured. The keys on `opts` are as follows: - initState: The last emitted state, used for restoring persistent state between sessions. -- platform: The `platform` object defines a variety of platform-specific functions, including opening the confirmation view, and customizing the encryption method. +- platform: The `platform` object defines a variety of platform-specific functions, including opening the confirmation view, and opening web sites. +- encryptor - An object that provides access to the desired encryption methods. + +##### Encryptor + +An object that provides two simple methods, which can encrypt in any format you prefer. This parameter is optional, and will default to the browser-native WebCrypto API. + +- encrypt(password, object) - returns a Promise of a string that is ready for storage. +- decrypt(password, encryptedString) - Accepts the encrypted output of `encrypt` and returns a Promise of a restored `object` as it was encrypted. + ##### Platform Options @@ -20,9 +29,6 @@ The `platform` object has a variety of options: - reload (function) - Will be called when MetaMask would like to reload its own context. - openWindow ({ url }) - Will be called when MetaMask would like to open a web page. It will be passed a single `options` object with a `url` key, with a string value. - getVersion() - Should return the current MetaMask version, as described in the current `CHANGELOG.md` or `app/manifest.json`. -- encryptor - An object that includes two methods: - - encrypt(password, object) - returns a Promise of a string that is ready for storage. - - decrypt(password, encryptedString) - Accepts the encrypted output of `encrypt` and returns a Promise of a restored `object` as it was encrypted. #### [metamask.getState()](https://github.com/MetaMask/metamask-extension/blob/master/app/scripts/metamask-controller.js#L241) From 977405fc7d89256a911e73b83a6678235fa1cfb8 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Fri, 22 Sep 2017 13:33:53 -0700 Subject: [PATCH 123/140] Remove dead code from eth-store --- app/scripts/lib/eth-store.js | 44 +----------------------------------- 1 file changed, 1 insertion(+), 43 deletions(-) diff --git a/app/scripts/lib/eth-store.js b/app/scripts/lib/eth-store.js index ebba98f5c..ff22eca4a 100644 --- a/app/scripts/lib/eth-store.js +++ b/app/scripts/lib/eth-store.js @@ -18,10 +18,6 @@ class EthereumStore extends ObservableStore { constructor (opts = {}) { super({ accounts: {}, - transactions: {}, - currentBlockNumber: '0', - currentBlockHash: '', - currentBlockGasLimit: '', }) this._provider = opts.provider this._query = new EthQuery(this._provider) @@ -50,21 +46,6 @@ class EthereumStore extends ObservableStore { this.updateState({ accounts }) } - addTransaction (txHash) { - const transactions = this.getState().transactions - transactions[txHash] = {} - this.updateState({ transactions }) - if (!this._currentBlockNumber) return - this._updateTransaction(this._currentBlockNumber, txHash, noop) - } - - removeTransaction (txHash) { - const transactions = this.getState().transactions - delete transactions[txHash] - this.updateState({ transactions }) - } - - // // private // @@ -72,12 +53,9 @@ class EthereumStore extends ObservableStore { _updateForBlock (block) { const blockNumber = '0x' + block.number.toString('hex') this._currentBlockNumber = blockNumber - this.updateState({ currentBlockNumber: parseInt(blockNumber) }) - this.updateState({ currentBlockHash: `0x${block.hash.toString('hex')}`}) - this.updateState({ currentBlockGasLimit: `0x${block.gasLimit.toString('hex')}` }) + async.parallel([ this._updateAccounts.bind(this), - this._updateTransactions.bind(this, blockNumber), ], (err) => { if (err) return console.error(err) this.emit('block', this.getState()) @@ -104,26 +82,6 @@ class EthereumStore extends ObservableStore { }) } - _updateTransactions (block, cb = noop) { - const transactions = this.getState().transactions - const txHashes = Object.keys(transactions) - async.each(txHashes, this._updateTransaction.bind(this, block), cb) - } - - _updateTransaction (block, txHash, cb = noop) { - // would use the block here to determine how many confirmations the tx has - const transactions = this.getState().transactions - this._query.getTransaction(txHash, (err, result) => { - if (err) return cb(err) - // only populate if the entry is still present - if (transactions[txHash]) { - transactions[txHash] = result - this.updateState({ transactions }) - } - cb(null, result) - }) - } - _getAccount (address, cb = noop) { const query = this._query async.parallel({ From 11c8c07bfc6677e347873f02ae8c401f8d6c4dcf Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Fri, 22 Sep 2017 13:59:25 -0700 Subject: [PATCH 124/140] Refactor eth-store into account-tracker EthStore was only being used for tracking account balances and nonces now, so I removed its block-tracking duties, renamed it account-tracker, and removed it as a dependency from `KeyringController`, so that KRC can go live on without a hard dep on it. --- .../{balances.js => computed-balances.js} | 4 +-- app/scripts/controllers/transactions.js | 6 ++--- .../lib/{eth-store.js => account-tracker.js} | 2 +- app/scripts/metamask-controller.js | 26 ++++++++++++------- 4 files changed, 22 insertions(+), 16 deletions(-) rename app/scripts/controllers/{balances.js => computed-balances.js} (95%) rename app/scripts/lib/{eth-store.js => account-tracker.js} (99%) diff --git a/app/scripts/controllers/balances.js b/app/scripts/controllers/computed-balances.js similarity index 95% rename from app/scripts/controllers/balances.js rename to app/scripts/controllers/computed-balances.js index 89c2ca95d..a85eb5590 100644 --- a/app/scripts/controllers/balances.js +++ b/app/scripts/controllers/computed-balances.js @@ -2,7 +2,7 @@ const ObservableStore = require('obs-store') const extend = require('xtend') const BalanceController = require('./balance') -class BalancesController { +class ComputedbalancesController { constructor (opts = {}) { const { ethStore, txController } = opts @@ -61,4 +61,4 @@ class BalancesController { } } -module.exports = BalancesController +module.exports = ComputedbalancesController diff --git a/app/scripts/controllers/transactions.js b/app/scripts/controllers/transactions.js index 59a3f5329..2aff4e5ff 100644 --- a/app/scripts/controllers/transactions.js +++ b/app/scripts/controllers/transactions.js @@ -22,7 +22,7 @@ module.exports = class TransactionController extends EventEmitter { this.provider = opts.provider this.blockTracker = opts.blockTracker this.signEthTx = opts.signTransaction - this.ethStore = opts.ethStore + this.accountTracker = opts.accountTracker this.nonceTracker = new NonceTracker({ provider: this.provider, @@ -52,7 +52,7 @@ module.exports = class TransactionController extends EventEmitter { provider: this.provider, nonceTracker: this.nonceTracker, getBalance: (address) => { - const account = this.ethStore.getState().accounts[address] + const account = this.accountTracker.getState().accounts[address] if (!account) return return account.balance }, @@ -73,7 +73,7 @@ module.exports = class TransactionController extends EventEmitter { 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 + // where accountTracker hasent been populated by the results yet this.blockTracker.once('latest', () => { this.blockTracker.on('latest', this.pendingTxTracker.resubmitPendingTxs.bind(this.pendingTxTracker)) }) diff --git a/app/scripts/lib/eth-store.js b/app/scripts/lib/account-tracker.js similarity index 99% rename from app/scripts/lib/eth-store.js rename to app/scripts/lib/account-tracker.js index ff22eca4a..bf949597b 100644 --- a/app/scripts/lib/eth-store.js +++ b/app/scripts/lib/account-tracker.js @@ -1,4 +1,4 @@ -/* Ethereum Store +/* Account Tracker * * This module is responsible for tracking any number of accounts * and caching their current balances & transaction counts. diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index 02c06ead2..b1cfe1a2d 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -4,7 +4,7 @@ const promiseToCallback = require('promise-to-callback') const pipe = require('pump') const Dnode = require('dnode') const ObservableStore = require('obs-store') -const EthStore = require('./lib/eth-store') +const AccountTracker = require('./lib/account-tracker') const EthQuery = require('eth-query') const streamIntoProvider = require('web3-stream-provider/handler') const setupMultiplex = require('./lib/stream-utils.js').setupMultiplex @@ -81,19 +81,25 @@ module.exports = class MetamaskController extends EventEmitter { // eth data query tools this.ethQuery = new EthQuery(this.provider) - this.ethStore = new EthStore({ - provider: this.provider, - blockTracker: this.provider, - }) // key mgmt this.keyringController = new KeyringController({ initState: initState.KeyringController, - ethStore: this.ethStore, + accountTracker: this.accountTracker, getNetwork: this.networkController.getNetworkState.bind(this.networkController), }) + + // account tracker watches balances, nonces, and any code at their address. + this.accountTracker = new AccountTracker({ + provider: this.provider, + blockTracker: this.provider, + }) this.keyringController.on('newAccount', (address) => { this.preferencesController.setSelectedAddress(address) + this.accountTracker.addAccount(address) + }) + this.keyringController.on('removedAccount', (address) => { + this.accountTracker.removeAccount(address) }) // address book controller @@ -112,13 +118,13 @@ module.exports = class MetamaskController extends EventEmitter { provider: this.provider, blockTracker: this.provider, ethQuery: this.ethQuery, - ethStore: this.ethStore, + accountTracker: this.accountTracker, }) this.txController.on('newUnaprovedTx', opts.showUnapprovedTx.bind(opts)) // computed balances (accounting for pending transactions) this.balancesController = new BalancesController({ - ethStore: this.ethStore, + accountTracker: this.accountTracker, txController: this.txController, }) this.networkController.on('networkDidChange', () => { @@ -177,7 +183,7 @@ module.exports = class MetamaskController extends EventEmitter { // manual mem state subscriptions this.networkController.store.subscribe(this.sendUpdate.bind(this)) - this.ethStore.subscribe(this.sendUpdate.bind(this)) + this.accountTracker.subscribe(this.sendUpdate.bind(this)) this.txController.memStore.subscribe(this.sendUpdate.bind(this)) this.balancesController.store.subscribe(this.sendUpdate.bind(this)) this.messageManager.memStore.subscribe(this.sendUpdate.bind(this)) @@ -260,7 +266,7 @@ module.exports = class MetamaskController extends EventEmitter { isInitialized, }, this.networkController.store.getState(), - this.ethStore.getState(), + this.accountTracker.getState(), this.txController.memStore.getState(), this.messageManager.memStore.getState(), this.personalMessageManager.memStore.getState(), From d2a747e57e591af5beb2e7cef7fc73a363d9c742 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Fri, 22 Sep 2017 14:06:54 -0700 Subject: [PATCH 125/140] Fix computed-balances controller reference --- app/scripts/metamask-controller.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index b1cfe1a2d..34d60d253 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -20,7 +20,7 @@ const BlacklistController = require('./controllers/blacklist') const MessageManager = require('./lib/message-manager') const PersonalMessageManager = require('./lib/personal-message-manager') const TransactionController = require('./controllers/transactions') -const BalancesController = require('./controllers/balances') +const BalancesController = require('./controllers/computed-balances') const ConfigManager = require('./lib/config-manager') const nodeify = require('./lib/nodeify') const accountImporter = require('./account-import-strategies') From f01b0a818ba67c2549f14382056534768a255e5b Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Fri, 22 Sep 2017 14:13:56 -0700 Subject: [PATCH 126/140] Fix account-tracker references --- app/scripts/controllers/balance.js | 6 +++--- app/scripts/controllers/computed-balances.js | 10 +++++----- app/scripts/keyring-controller.js | 10 +++++----- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/app/scripts/controllers/balance.js b/app/scripts/controllers/balance.js index b4e72e751..ddeb06cf9 100644 --- a/app/scripts/controllers/balance.js +++ b/app/scripts/controllers/balance.js @@ -5,9 +5,9 @@ const BN = require('ethereumjs-util').BN class BalanceController { constructor (opts = {}) { - const { address, ethStore, txController } = opts + const { address, accountTracker, txController } = opts this.address = address - this.ethStore = ethStore + this.accountTracker = accountTracker this.txController = txController const initState = { @@ -39,7 +39,7 @@ class BalanceController { } _getBalance () { - const store = this.ethStore.getState() + const store = this.accountTracker.getState() const balances = store.accounts const entry = balances[this.address] const balance = entry.balance diff --git a/app/scripts/controllers/computed-balances.js b/app/scripts/controllers/computed-balances.js index a85eb5590..576746164 100644 --- a/app/scripts/controllers/computed-balances.js +++ b/app/scripts/controllers/computed-balances.js @@ -5,8 +5,8 @@ const BalanceController = require('./balance') class ComputedbalancesController { constructor (opts = {}) { - const { ethStore, txController } = opts - this.ethStore = ethStore + const { accountTracker, txController } = opts + this.accountTracker = accountTracker this.txController = txController const initState = extend({ @@ -25,9 +25,9 @@ class ComputedbalancesController { } _initBalanceUpdating () { - const store = this.ethStore.getState() + const store = this.accountTracker.getState() this.addAnyAccountsFromStore(store) - this.ethStore.subscribe(this.addAnyAccountsFromStore.bind(this)) + this.accountTracker.subscribe(this.addAnyAccountsFromStore.bind(this)) } addAnyAccountsFromStore(store) { @@ -48,7 +48,7 @@ class ComputedbalancesController { trackAddress (address) { let updater = new BalanceController({ address, - ethStore: this.ethStore, + accountTracker: this.accountTracker, txController: this.txController, }) updater.store.subscribe((accountBalance) => { diff --git a/app/scripts/keyring-controller.js b/app/scripts/keyring-controller.js index fd57fac70..fa470dd89 100644 --- a/app/scripts/keyring-controller.js +++ b/app/scripts/keyring-controller.js @@ -35,7 +35,7 @@ class KeyringController extends EventEmitter { keyrings: [], identities: {}, }) - this.ethStore = opts.ethStore + this.accountTracker = opts.accountTracker this.encryptor = encryptor this.keyrings = [] this.getNetwork = opts.getNetwork @@ -338,7 +338,7 @@ class KeyringController extends EventEmitter { // // Initializes the provided account array // Gives them numerically incremented nicknames, - // and adds them to the ethStore for regular balance checking. + // and adds them to the accountTracker for regular balance checking. setupAccounts (accounts) { return this.getAccounts() .then((loadedAccounts) => { @@ -361,7 +361,7 @@ class KeyringController extends EventEmitter { throw new Error('Problem loading account.') } const address = normalizeAddress(account) - this.ethStore.addAccount(address) + this.accountTracker.addAccount(address) return this.createNickname(address) } @@ -567,12 +567,12 @@ class KeyringController extends EventEmitter { clearKeyrings () { let accounts try { - accounts = Object.keys(this.ethStore.getState()) + accounts = Object.keys(this.accountTracker.getState()) } catch (e) { accounts = [] } accounts.forEach((address) => { - this.ethStore.removeAccount(address) + this.accountTracker.removeAccount(address) }) // clear keyrings from memory From 128cf40f91df6d78e2d5ca87608fe16e91a510fa Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Fri, 22 Sep 2017 14:16:19 -0700 Subject: [PATCH 127/140] Fix accont-tracker merge bug --- app/scripts/metamask-controller.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index 2fc5b4204..1dd09d0ad 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -86,6 +86,10 @@ module.exports = class MetamaskController extends EventEmitter { // eth data query tools this.ethQuery = new EthQuery(this.provider) + this.accountTracker = new AccountTracker({ + provider: this.provider, + blockTracker: this.blockTracker, + }) // key mgmt this.keyringController = new KeyringController({ From f128240e7f877280fa59bf22f2ea8285bb467022 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Fri, 22 Sep 2017 14:19:14 -0700 Subject: [PATCH 128/140] Fix test references --- test/unit/keyring-controller-test.js | 2 +- test/unit/tx-controller-test.js | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/test/unit/keyring-controller-test.js b/test/unit/keyring-controller-test.js index 2d9a53723..34c314639 100644 --- a/test/unit/keyring-controller-test.js +++ b/test/unit/keyring-controller-test.js @@ -24,7 +24,7 @@ describe('KeyringController', function () { getTxList: () => [], getUnapprovedTxList: () => [], }, - ethStore: { + accountTracker: { addAccount (acct) { accounts.push(ethUtil.addHexPrefix(acct)) }, }, }) diff --git a/test/unit/tx-controller-test.js b/test/unit/tx-controller-test.js index 7bb193242..7b875db66 100644 --- a/test/unit/tx-controller-test.js +++ b/test/unit/tx-controller-test.js @@ -27,7 +27,7 @@ describe('Transaction Controller', function () { networkStore: new ObservableStore(currentNetworkId), txHistoryLimit: 10, blockTracker: { getCurrentBlock: noop, on: noop, once: noop }, - ethStore: { getState: noop }, + accountTracker: { getState: noop }, signTransaction: (ethTx) => new Promise((resolve) => { ethTx.sign(privKey) resolve() @@ -431,4 +431,4 @@ describe('Transaction Controller', function () { }).catch(done) }) }) -}) \ No newline at end of file +}) From 40f1d0868401662c42f6a031549c9b023427ccef Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Mon, 25 Sep 2017 11:42:08 -0700 Subject: [PATCH 129/140] Made some requested changes --- app/scripts/controllers/balance.js | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/app/scripts/controllers/balance.js b/app/scripts/controllers/balance.js index ddeb06cf9..840b7abc3 100644 --- a/app/scripts/controllers/balance.js +++ b/app/scripts/controllers/balance.js @@ -16,7 +16,7 @@ class BalanceController { this.store = new ObservableStore(initState) this.balanceCalc = new PendingBalanceCalculator({ - getBalance: () => Promise.resolve(this._getBalance()), + getBalance: () => this._getBalance(), getPendingTransactions: this._getPendingTransactions.bind(this), }) @@ -35,24 +35,24 @@ class BalanceController { this.txController.on('submitted', update) this.txController.on('confirmed', update) this.txController.on('failed', update) + this.accountTracker.subscribe(update) this.txController.blockTracker.on('block', update) } - _getBalance () { - const store = this.accountTracker.getState() - const balances = store.accounts - const entry = balances[this.address] + async _getBalance () { + const { accounts } = this.accountTracker.getState() + const entry = accounts[this.address] const balance = entry.balance return balance ? new BN(balance.substring(2), 16) : undefined } - _getPendingTransactions () { + async _getPendingTransactions () { const pending = this.txController.getFilteredTxList({ from: this.address, status: 'submitted', err: undefined, }) - return Promise.resolve(pending) + return pending } } From 8cd7329c91b047ef15c81b164075ea6c1d15b0df Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Mon, 25 Sep 2017 14:36:49 -0700 Subject: [PATCH 130/140] Implemented feedback --- app/scripts/controllers/balance.js | 4 ++-- app/scripts/lib/pending-balance-calculator.js | 8 +++----- test/unit/pending-balance-test.js | 20 +++++++------------ 3 files changed, 12 insertions(+), 20 deletions(-) diff --git a/app/scripts/controllers/balance.js b/app/scripts/controllers/balance.js index 840b7abc3..9b2566852 100644 --- a/app/scripts/controllers/balance.js +++ b/app/scripts/controllers/balance.js @@ -20,7 +20,7 @@ class BalanceController { getPendingTransactions: this._getPendingTransactions.bind(this), }) - this.registerUpdates() + this._registerUpdates() } async updateBalance () { @@ -30,7 +30,7 @@ class BalanceController { }) } - registerUpdates () { + _registerUpdates () { const update = this.updateBalance.bind(this) this.txController.on('submitted', update) this.txController.on('confirmed', update) diff --git a/app/scripts/lib/pending-balance-calculator.js b/app/scripts/lib/pending-balance-calculator.js index c66bffbbb..cea642f1a 100644 --- a/app/scripts/lib/pending-balance-calculator.js +++ b/app/scripts/lib/pending-balance-calculator.js @@ -19,19 +19,17 @@ class PendingBalanceCalculator { this.getPendingTransactions(), ]) - const balance = results[0] - const pending = results[1] - + const [ balance, pending ] = results if (!balance) return undefined const pendingValue = pending.reduce((total, tx) => { - return total.add(this.valueFor(tx)) + return total.add(this.calculateMaxCost(tx)) }, new BN(0)) return `0x${balance.sub(pendingValue).toString(16)}` } - valueFor (tx) { + calculateMaxCost (tx) { const txValue = tx.txParams.value const value = this.hexToBn(txValue) const gasPrice = this.hexToBn(tx.txParams.gasPrice) diff --git a/test/unit/pending-balance-test.js b/test/unit/pending-balance-test.js index dde30fecc..5048d487b 100644 --- a/test/unit/pending-balance-test.js +++ b/test/unit/pending-balance-test.js @@ -11,7 +11,7 @@ const ether = '0x' + etherBn.toString(16) describe('PendingBalanceCalculator', function () { let balanceCalculator - describe('#valueFor(tx)', function () { + describe('#calculateMaxCost(tx)', function () { it('returns a BN for a given tx value', function () { const txGen = new MockTxGen() pendingTxs = txGen.generate({ @@ -24,7 +24,7 @@ describe('PendingBalanceCalculator', function () { }, { count: 1 }) const balanceCalculator = generateBalanceCalcWith([], zeroBn) - const result = balanceCalculator.valueFor(pendingTxs[0]) + const result = balanceCalculator.calculateMaxCost(pendingTxs[0]) assert.equal(result.toString(), etherBn.toString(), 'computes one ether') }) @@ -40,8 +40,8 @@ describe('PendingBalanceCalculator', function () { }, { count: 1 }) const balanceCalculator = generateBalanceCalcWith([], zeroBn) - const result = balanceCalculator.valueFor(pendingTxs[0]) - assert.equal(result.toString(), '6', 'computes one ether') + const result = balanceCalculator.calculateMaxCost(pendingTxs[0]) + assert.equal(result.toString(), '6', 'computes 6 wei of gas') }) }) @@ -82,15 +82,9 @@ describe('PendingBalanceCalculator', function () { }) function generateBalanceCalcWith (transactions, providerStub = zeroBn) { - const getPendingTransactions = () => Promise.resolve(transactions) - const getBalance = () => Promise.resolve(providerStub) - providerResultStub.result = providerStub - const provider = { - sendAsync: (_, cb) => { cb(undefined, providerResultStub) }, - _blockTracker: { - getCurrentBlock: () => '0x11b568', - }, - } + const getPendingTransactions = async () => transactions + const getBalance = async () => providerStub + return new PendingBalanceCalculator({ getBalance, getPendingTransactions, From 674aac83ce49d21606be7be7afdf1b6a8ceb386f Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Mon, 25 Sep 2017 14:39:22 -0700 Subject: [PATCH 131/140] Make blockTracker an independent param --- app/scripts/controllers/balance.js | 5 +++-- app/scripts/controllers/computed-balances.js | 4 +++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/app/scripts/controllers/balance.js b/app/scripts/controllers/balance.js index 9b2566852..ab0cfe907 100644 --- a/app/scripts/controllers/balance.js +++ b/app/scripts/controllers/balance.js @@ -5,10 +5,11 @@ const BN = require('ethereumjs-util').BN class BalanceController { constructor (opts = {}) { - const { address, accountTracker, txController } = opts + const { address, accountTracker, txController, blockTracker } = opts this.address = address this.accountTracker = accountTracker this.txController = txController + this.blockTracker = blockTracker const initState = { ethBalance: undefined, @@ -36,7 +37,7 @@ class BalanceController { this.txController.on('confirmed', update) this.txController.on('failed', update) this.accountTracker.subscribe(update) - this.txController.blockTracker.on('block', update) + this.blockTracker.on('block', update) } async _getBalance () { diff --git a/app/scripts/controllers/computed-balances.js b/app/scripts/controllers/computed-balances.js index 576746164..2b27d128d 100644 --- a/app/scripts/controllers/computed-balances.js +++ b/app/scripts/controllers/computed-balances.js @@ -5,9 +5,10 @@ const BalanceController = require('./balance') class ComputedbalancesController { constructor (opts = {}) { - const { accountTracker, txController } = opts + const { accountTracker, txController, blockTracker } = opts this.accountTracker = accountTracker this.txController = txController + this.blockTracker = blockTracker const initState = extend({ computedBalances: {}, @@ -50,6 +51,7 @@ class ComputedbalancesController { address, accountTracker: this.accountTracker, txController: this.txController, + blockTracker: this.blockTracker, }) updater.store.subscribe((accountBalance) => { let newState = this.store.getState() From feed9a5a17f89ee319dc5558634e8c5c07b2ce65 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Mon, 25 Sep 2017 14:45:28 -0700 Subject: [PATCH 132/140] Add mock random value generator --- test/lib/mock-encryptor.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/test/lib/mock-encryptor.js b/test/lib/mock-encryptor.js index cdf13c507..ef229a82f 100644 --- a/test/lib/mock-encryptor.js +++ b/test/lib/mock-encryptor.js @@ -29,4 +29,8 @@ module.exports = { return 'WHADDASALT!' }, + getRandomValues () { + return 'SOO RANDO!!!1' + } + } From 1968d61431183300298f3ea17b5092865cb915bf Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Mon, 25 Sep 2017 15:23:37 -0700 Subject: [PATCH 133/140] Make encryptor configurable for keyring-controller --- app/scripts/keyring-controller.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/scripts/keyring-controller.js b/app/scripts/keyring-controller.js index 2a1af6e29..34e008ec4 100644 --- a/app/scripts/keyring-controller.js +++ b/app/scripts/keyring-controller.js @@ -37,7 +37,7 @@ class KeyringController extends EventEmitter { }) this.accountTracker = opts.accountTracker - this.encryptor = encryptor + this.encryptor = opts.encryptor || encryptor this.keyrings = [] this.getNetwork = opts.getNetwork } From e52d52b22ed00b1a761bf57cff54c7276c6450a7 Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Tue, 26 Sep 2017 08:22:48 +0000 Subject: [PATCH 134/140] chore(package): update sinon to version 4.0.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index bcfb6c1ac..09490096b 100644 --- a/package.json +++ b/package.json @@ -196,7 +196,7 @@ "react-addons-test-utils": "^15.5.1", "react-test-renderer": "^15.5.4", "react-testutils-additions": "^15.2.0", - "sinon": "^3.2.0", + "sinon": "^4.0.0", "tape": "^4.5.1", "testem": "^1.10.3", "uglifyify": "^4.0.2", From b46cb3ecb5a3a1b4a197f69960f932d69287aa62 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Tue, 26 Sep 2017 09:23:46 -0700 Subject: [PATCH 135/140] Fix token precision bug Had fixed this before in the dependency, but hadn't merged in that version bump yet :( Fixes #2162 --- CHANGELOG.md | 2 ++ package.json | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e692c58dc..f04136d78 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Current Master +- Fix bug that could mis-render token balances when very small. (Not actually included in 3.9.9) + ## 3.10.3 2017-9-21 - Fix bug where metamask-dapp connections are lost on rpc error diff --git a/package.json b/package.json index bcfb6c1ac..5599f2c20 100644 --- a/package.json +++ b/package.json @@ -76,7 +76,7 @@ "eth-query": "^2.1.2", "eth-sig-util": "^1.2.2", "eth-simple-keyring": "^1.1.1", - "eth-token-tracker": "^1.1.3", + "eth-token-tracker": "^1.1.4", "ethereumjs-tx": "^1.3.0", "ethereumjs-util": "github:ethereumjs/ethereumjs-util#ac5d0908536b447083ea422b435da27f26615de9", "ethereumjs-wallet": "^0.6.0", From 5d300f146a679ba1a639ee9a568e8452c886c736 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Tue, 26 Sep 2017 09:38:43 -0700 Subject: [PATCH 136/140] Add computed balance to mock state --- development/states/first-time.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/development/states/first-time.json b/development/states/first-time.json index 683a61fdf..b2cc8ef8f 100644 --- a/development/states/first-time.json +++ b/development/states/first-time.json @@ -4,6 +4,7 @@ "isUnlocked": false, "rpcTarget": "https://rawtestrpc.metamask.io/", "identities": {}, + "computedBalances": {}, "frequentRpcList": [], "unapprovedTxs": {}, "currentCurrency": "USD", @@ -48,5 +49,6 @@ "isLoading": false, "warning": null }, - "identities": {} + "identities": {}, + "computedBalances": {} } From 9e3648c668aed1f3e632efe1693d6a2e0aa76617 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Tue, 26 Sep 2017 11:33:36 -0700 Subject: [PATCH 137/140] Pass blocktracker to balances controller --- app/scripts/metamask-controller.js | 1 + 1 file changed, 1 insertion(+) diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index 30e511e19..cca796678 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -136,6 +136,7 @@ module.exports = class MetamaskController extends EventEmitter { this.balancesController = new BalancesController({ accountTracker: this.accountTracker, txController: this.txController, + blockTracker: this.blockTracker, }) this.networkController.on('networkDidChange', () => { this.balancesController.updateAllBalances() From 3bedcd3582519c7afbb8164b40acca4b96eab4bf Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Tue, 26 Sep 2017 13:36:41 -0700 Subject: [PATCH 138/140] Restore blockGasLimit to account-tracker --- app/scripts/lib/account-tracker.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/scripts/lib/account-tracker.js b/app/scripts/lib/account-tracker.js index bf949597b..3df5fbc9d 100644 --- a/app/scripts/lib/account-tracker.js +++ b/app/scripts/lib/account-tracker.js @@ -18,6 +18,7 @@ class EthereumStore extends ObservableStore { constructor (opts = {}) { super({ accounts: {}, + currentBlockGasLimit: '', }) this._provider = opts.provider this._query = new EthQuery(this._provider) @@ -54,6 +55,8 @@ class EthereumStore extends ObservableStore { const blockNumber = '0x' + block.number.toString('hex') this._currentBlockNumber = blockNumber + this.updateState({ currentBlockGasLimit: `0x${block.gasLimit.toString('hex')}` }) + async.parallel([ this._updateAccounts.bind(this), ], (err) => { From 2eca5455c0c80d99b10c7d56858f84e605494fba Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Tue, 26 Sep 2017 14:15:16 -0700 Subject: [PATCH 139/140] Move obs store into account-tracker instead of inheriting --- app/scripts/controllers/balance.js | 4 +-- app/scripts/controllers/computed-balances.js | 6 ++-- app/scripts/lib/account-tracker.js | 31 ++++++++++++-------- app/scripts/metamask-controller.js | 4 +-- 4 files changed, 25 insertions(+), 20 deletions(-) diff --git a/app/scripts/controllers/balance.js b/app/scripts/controllers/balance.js index ab0cfe907..964dff0df 100644 --- a/app/scripts/controllers/balance.js +++ b/app/scripts/controllers/balance.js @@ -36,12 +36,12 @@ class BalanceController { this.txController.on('submitted', update) this.txController.on('confirmed', update) this.txController.on('failed', update) - this.accountTracker.subscribe(update) + this.accountTracker.store.subscribe(update) this.blockTracker.on('block', update) } async _getBalance () { - const { accounts } = this.accountTracker.getState() + const { accounts } = this.accountTracker.store.getState() const entry = accounts[this.address] const balance = entry.balance return balance ? new BN(balance.substring(2), 16) : undefined diff --git a/app/scripts/controllers/computed-balances.js b/app/scripts/controllers/computed-balances.js index 2b27d128d..2479e1b3a 100644 --- a/app/scripts/controllers/computed-balances.js +++ b/app/scripts/controllers/computed-balances.js @@ -20,15 +20,15 @@ class ComputedbalancesController { } updateAllBalances () { - for (let address in this.balances) { + for (let address in this.accountTracker.store.getState().accounts) { this.balances[address].updateBalance() } } _initBalanceUpdating () { - const store = this.accountTracker.getState() + const store = this.accountTracker.store.getState() this.addAnyAccountsFromStore(store) - this.accountTracker.subscribe(this.addAnyAccountsFromStore.bind(this)) + this.accountTracker.store.subscribe(this.addAnyAccountsFromStore.bind(this)) } addAnyAccountsFromStore(store) { diff --git a/app/scripts/lib/account-tracker.js b/app/scripts/lib/account-tracker.js index 3df5fbc9d..e2892b1ce 100644 --- a/app/scripts/lib/account-tracker.js +++ b/app/scripts/lib/account-tracker.js @@ -10,16 +10,21 @@ const async = require('async') const EthQuery = require('eth-query') const ObservableStore = require('obs-store') +const EventEmitter = require('events').EventEmitter function noop () {} -class EthereumStore extends ObservableStore { +class AccountTracker extends EventEmitter { constructor (opts = {}) { - super({ + super() + + const initState = { accounts: {}, currentBlockGasLimit: '', - }) + } + this.store = new ObservableStore(initState) + this._provider = opts.provider this._query = new EthQuery(this._provider) this._blockTracker = opts.blockTracker @@ -34,17 +39,17 @@ class EthereumStore extends ObservableStore { // addAccount (address) { - const accounts = this.getState().accounts + const accounts = this.store.getState().accounts accounts[address] = {} - this.updateState({ accounts }) + this.store.updateState({ accounts }) if (!this._currentBlockNumber) return this._updateAccount(address) } removeAccount (address) { - const accounts = this.getState().accounts + const accounts = this.store.getState().accounts delete accounts[address] - this.updateState({ accounts }) + this.store.updateState({ accounts }) } // @@ -55,31 +60,31 @@ class EthereumStore extends ObservableStore { const blockNumber = '0x' + block.number.toString('hex') this._currentBlockNumber = blockNumber - this.updateState({ currentBlockGasLimit: `0x${block.gasLimit.toString('hex')}` }) + this.store.updateState({ currentBlockGasLimit: `0x${block.gasLimit.toString('hex')}` }) async.parallel([ this._updateAccounts.bind(this), ], (err) => { if (err) return console.error(err) - this.emit('block', this.getState()) + this.emit('block', this.store.getState()) }) } _updateAccounts (cb = noop) { - const accounts = this.getState().accounts + const accounts = this.store.getState().accounts const addresses = Object.keys(accounts) async.each(addresses, this._updateAccount.bind(this), cb) } _updateAccount (address, cb = noop) { - const accounts = this.getState().accounts this._getAccount(address, (err, result) => { if (err) return cb(err) result.address = address + const accounts = this.store.getState().accounts // only populate if the entry is still present if (accounts[address]) { accounts[address] = result - this.updateState({ accounts }) + this.store.updateState({ accounts }) } cb(null, result) }) @@ -96,4 +101,4 @@ class EthereumStore extends ObservableStore { } -module.exports = EthereumStore +module.exports = AccountTracker diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index cca796678..a86d8d37b 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -194,7 +194,7 @@ module.exports = class MetamaskController extends EventEmitter { // manual mem state subscriptions this.networkController.store.subscribe(this.sendUpdate.bind(this)) - this.accountTracker.subscribe(this.sendUpdate.bind(this)) + this.accountTracker.store.subscribe(this.sendUpdate.bind(this)) this.txController.memStore.subscribe(this.sendUpdate.bind(this)) this.balancesController.store.subscribe(this.sendUpdate.bind(this)) this.messageManager.memStore.subscribe(this.sendUpdate.bind(this)) @@ -277,7 +277,7 @@ module.exports = class MetamaskController extends EventEmitter { isInitialized, }, this.networkController.store.getState(), - this.accountTracker.getState(), + this.accountTracker.store.getState(), this.txController.memStore.getState(), this.messageManager.memStore.getState(), this.personalMessageManager.memStore.getState(), From 651098c70d21edbca98a96ef2a8800d164035638 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Tue, 26 Sep 2017 14:30:29 -0700 Subject: [PATCH 140/140] Remove duplicate instantiation of account-tracker --- app/scripts/metamask-controller.js | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index a86d8d37b..0f850b7f5 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -86,6 +86,7 @@ module.exports = class MetamaskController extends EventEmitter { // eth data query tools this.ethQuery = new EthQuery(this.provider) + // account tracker watches balances, nonces, and any code at their address. this.accountTracker = new AccountTracker({ provider: this.provider, blockTracker: this.blockTracker, @@ -99,11 +100,6 @@ module.exports = class MetamaskController extends EventEmitter { encryptor: opts.encryptor || undefined, }) - // account tracker watches balances, nonces, and any code at their address. - this.accountTracker = new AccountTracker({ - provider: this.provider, - blockTracker: this.provider, - }) this.keyringController.on('newAccount', (address) => { this.preferencesController.setSelectedAddress(address) this.accountTracker.addAccount(address)