aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorkumavis <aaron@kumavis.me>2017-09-27 12:42:30 +0800
committerkumavis <aaron@kumavis.me>2017-09-27 12:42:30 +0800
commit1877c0766c1f3ab46ec89dad0200fb8f75f52bae (patch)
tree44243d257dfb7f787882578fa14df8f01900fb45
parent443b1a8eb7883b6799692ddc24d0555d49bd1787 (diff)
parent6ca519e97c4c282023ab6b7788715ff8d7ec8189 (diff)
downloadtangerine-wallet-browser-1877c0766c1f3ab46ec89dad0200fb8f75f52bae.tar.gz
tangerine-wallet-browser-1877c0766c1f3ab46ec89dad0200fb8f75f52bae.tar.zst
tangerine-wallet-browser-1877c0766c1f3ab46ec89dad0200fb8f75f52bae.zip
Merge branch 'master' of github.com:MetaMask/metamask-extension into BreakOutKeyringController
-rw-r--r--CHANGELOG.md4
-rw-r--r--app/manifest.json2
-rw-r--r--app/scripts/controllers/balance.js23
-rw-r--r--app/scripts/controllers/computed-balances.js10
-rw-r--r--app/scripts/keyring-controller.js596
-rw-r--r--app/scripts/lib/account-tracker.js32
-rw-r--r--app/scripts/lib/pending-balance-calculator.js8
-rw-r--r--app/scripts/metamask-controller.js11
-rw-r--r--development/states/first-time.json4
-rw-r--r--package.json5
-rw-r--r--test/lib/mock-encryptor.js4
-rw-r--r--test/unit/pending-balance-test.js20
12 files changed, 663 insertions, 56 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 3ff062cf8..f04136d78 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,10 @@
## 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
- 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",
diff --git a/app/scripts/controllers/balance.js b/app/scripts/controllers/balance.js
index ddeb06cf9..964dff0df 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,
@@ -16,11 +17,11 @@ class BalanceController {
this.store = new ObservableStore(initState)
this.balanceCalc = new PendingBalanceCalculator({
- getBalance: () => Promise.resolve(this._getBalance()),
+ getBalance: () => this._getBalance(),
getPendingTransactions: this._getPendingTransactions.bind(this),
})
- this.registerUpdates()
+ this._registerUpdates()
}
async updateBalance () {
@@ -30,29 +31,29 @@ class BalanceController {
})
}
- registerUpdates () {
+ _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)
+ this.accountTracker.store.subscribe(update)
+ this.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.store.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
}
}
diff --git a/app/scripts/controllers/computed-balances.js b/app/scripts/controllers/computed-balances.js
index 576746164..2479e1b3a 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: {},
@@ -19,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) {
@@ -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()
diff --git a/app/scripts/keyring-controller.js b/app/scripts/keyring-controller.js
new file mode 100644
index 000000000..34e008ec4
--- /dev/null
+++ b/app/scripts/keyring-controller.js
@@ -0,0 +1,596 @@
+const ethUtil = require('ethereumjs-util')
+const BN = ethUtil.BN
+const bip39 = require('bip39')
+const EventEmitter = require('events').EventEmitter
+const ObservableStore = require('obs-store')
+const filter = require('promise-filter')
+const encryptor = require('browser-passworder')
+const sigUtil = require('eth-sig-util')
+const normalizeAddress = sigUtil.normalize
+// Keyrings:
+const SimpleKeyring = require('eth-simple-keyring')
+const HdKeyring = require('eth-hd-keyring')
+const keyringTypes = [
+ SimpleKeyring,
+ HdKeyring,
+]
+
+class KeyringController extends EventEmitter {
+
+ // PUBLIC METHODS
+ //
+ // THE FIRST SECTION OF METHODS ARE PUBLIC-FACING,
+ // MEANING THEY ARE USED BY CONSUMERS OF THIS CLASS.
+ //
+ // THEIR SURFACE AREA SHOULD BE CHANGED WITH GREAT CARE.
+
+ constructor (opts) {
+ super()
+ const initState = opts.initState || {}
+ this.keyringTypes = keyringTypes
+ this.store = new ObservableStore(initState)
+ this.memStore = new ObservableStore({
+ isUnlocked: false,
+ keyringTypes: this.keyringTypes.map(krt => krt.type),
+ keyrings: [],
+ identities: {},
+ })
+
+ this.accountTracker = opts.accountTracker
+ this.encryptor = opts.encryptor || encryptor
+ this.keyrings = []
+ this.getNetwork = opts.getNetwork
+ }
+
+ // Full Update
+ // returns Promise( @object state )
+ //
+ // Emits the `update` event and
+ // returns a Promise that resolves to the current state.
+ //
+ // Frequently used to end asynchronous chains in this class,
+ // indicating consumers can often either listen for updates,
+ // or accept a state-resolving promise to consume their results.
+ //
+ // Not all methods end with this, that might be a nice refactor.
+ fullUpdate () {
+ this.emit('update')
+ return Promise.resolve(this.memStore.getState())
+ }
+
+ // Create New Vault And Keychain
+ // @string password - The password to encrypt the vault with
+ //
+ // returns Promise( @object state )
+ //
+ // Destroys any old encrypted storage,
+ // creates a new encrypted store with the given password,
+ // randomly creates a new HD wallet with 1 account,
+ // faucets that account on the testnet.
+ createNewVaultAndKeychain (password) {
+ return this.persistAllKeyrings(password)
+ .then(this.createFirstKeyTree.bind(this))
+ .then(this.fullUpdate.bind(this))
+ }
+
+ // CreateNewVaultAndRestore
+ // @string password - The password to encrypt the vault with
+ // @string seed - The BIP44-compliant seed phrase.
+ //
+ // returns Promise( @object state )
+ //
+ // Destroys any old encrypted storage,
+ // creates a new encrypted store with the given password,
+ // creates a new HD wallet from the given seed with 1 account.
+ createNewVaultAndRestore (password, seed) {
+ if (typeof password !== 'string') {
+ return Promise.reject('Password must be text.')
+ }
+
+ if (!bip39.validateMnemonic(seed)) {
+ return Promise.reject(new Error('Seed phrase is invalid.'))
+ }
+
+ this.clearKeyrings()
+
+ return this.persistAllKeyrings(password)
+ .then(() => {
+ return this.addNewKeyring('HD Key Tree', {
+ mnemonic: seed,
+ numberOfAccounts: 1,
+ })
+ })
+ .then((firstKeyring) => {
+ return firstKeyring.getAccounts()
+ })
+ .then((accounts) => {
+ const firstAccount = accounts[0]
+ if (!firstAccount) throw new Error('KeyringController - First Account not found.')
+ const hexAccount = normalizeAddress(firstAccount)
+ this.emit('newAccount', hexAccount)
+ return this.setupAccounts(accounts)
+ })
+ .then(this.persistAllKeyrings.bind(this, password))
+ .then(this.fullUpdate.bind(this))
+ }
+
+ // Set Locked
+ // returns Promise( @object state )
+ //
+ // This method deallocates all secrets, and effectively locks metamask.
+ setLocked () {
+ // set locked
+ this.password = null
+ this.memStore.updateState({ isUnlocked: false })
+ // remove keyrings
+ this.keyrings = []
+ this._updateMemStoreKeyrings()
+ return this.fullUpdate()
+ }
+
+ // Submit Password
+ // @string password
+ //
+ // returns Promise( @object state )
+ //
+ // Attempts to decrypt the current vault and load its keyrings
+ // into memory.
+ //
+ // Temporarily also migrates any old-style vaults first, as well.
+ // (Pre MetaMask 3.0.0)
+ submitPassword (password) {
+ return this.unlockKeyrings(password)
+ .then((keyrings) => {
+ this.keyrings = keyrings
+ return this.fullUpdate()
+ })
+ }
+
+ // Add New Keyring
+ // @string type
+ // @object opts
+ //
+ // returns Promise( @Keyring keyring )
+ //
+ // Adds a new Keyring of the given `type` to the vault
+ // and the current decrypted Keyrings array.
+ //
+ // All Keyring classes implement a unique `type` string,
+ // and this is used to retrieve them from the keyringTypes array.
+ addNewKeyring (type, opts) {
+ const Keyring = this.getKeyringClassForType(type)
+ const keyring = new Keyring(opts)
+ return keyring.deserialize(opts)
+ .then(() => {
+ return keyring.getAccounts()
+ })
+ .then((accounts) => {
+ return this.checkForDuplicate(type, accounts)
+ })
+ .then((checkedAccounts) => {
+ this.keyrings.push(keyring)
+ return this.setupAccounts(checkedAccounts)
+ })
+ .then(() => this.persistAllKeyrings())
+ .then(() => this._updateMemStoreKeyrings())
+ .then(() => this.fullUpdate())
+ .then(() => {
+ return keyring
+ })
+ }
+
+ // For now just checks for simple key pairs
+ // but in the future
+ // should possibly add HD and other types
+ //
+ checkForDuplicate (type, newAccount) {
+ return this.getAccounts()
+ .then((accounts) => {
+ switch (type) {
+ case 'Simple Key Pair':
+ const isNotIncluded = !accounts.find((key) => key === newAccount[0] || key === ethUtil.stripHexPrefix(newAccount[0]))
+ return (isNotIncluded) ? Promise.resolve(newAccount) : Promise.reject(new Error('The account you\'re are trying to import is a duplicate'))
+ default:
+ return Promise.resolve(newAccount)
+ }
+ })
+ }
+
+
+ // Add New Account
+ // @number keyRingNum
+ //
+ // returns Promise( @object state )
+ //
+ // Calls the `addAccounts` method on the Keyring
+ // in the kryings array at index `keyringNum`,
+ // and then saves those changes.
+ addNewAccount (selectedKeyring) {
+ return selectedKeyring.addAccounts(1)
+ .then(this.setupAccounts.bind(this))
+ .then(this.persistAllKeyrings.bind(this))
+ .then(this._updateMemStoreKeyrings.bind(this))
+ .then(this.fullUpdate.bind(this))
+ }
+
+ // Save Account Label
+ // @string account
+ // @string label
+ //
+ // returns Promise( @string label )
+ //
+ // Persists a nickname equal to `label` for the specified account.
+ saveAccountLabel (account, label) {
+ try {
+ const hexAddress = normalizeAddress(account)
+ // update state on diskStore
+ const state = this.store.getState()
+ const walletNicknames = state.walletNicknames || {}
+ walletNicknames[hexAddress] = label
+ this.store.updateState({ walletNicknames })
+ // update state on memStore
+ const identities = this.memStore.getState().identities
+ identities[hexAddress].name = label
+ this.memStore.updateState({ identities })
+ return Promise.resolve(label)
+ } catch (err) {
+ return Promise.reject(err)
+ }
+ }
+
+ // Export Account
+ // @string address
+ //
+ // returns Promise( @string privateKey )
+ //
+ // Requests the private key from the keyring controlling
+ // the specified address.
+ //
+ // Returns a Promise that may resolve with the private key string.
+ exportAccount (address) {
+ try {
+ return this.getKeyringForAccount(address)
+ .then((keyring) => {
+ return keyring.exportAccount(normalizeAddress(address))
+ })
+ } catch (e) {
+ return Promise.reject(e)
+ }
+ }
+
+
+ // SIGNING METHODS
+ //
+ // This method signs tx and returns a promise for
+ // TX Manager to update the state after signing
+
+ signTransaction (ethTx, _fromAddress) {
+ const fromAddress = normalizeAddress(_fromAddress)
+ return this.getKeyringForAccount(fromAddress)
+ .then((keyring) => {
+ return keyring.signTransaction(fromAddress, ethTx)
+ })
+ }
+
+ // Sign Message
+ // @object msgParams
+ //
+ // returns Promise(@buffer rawSig)
+ //
+ // Attempts to sign the provided @object msgParams.
+ signMessage (msgParams) {
+ const address = normalizeAddress(msgParams.from)
+ return this.getKeyringForAccount(address)
+ .then((keyring) => {
+ return keyring.signMessage(address, msgParams.data)
+ })
+ }
+
+ // Sign Personal Message
+ // @object msgParams
+ //
+ // returns Promise(@buffer rawSig)
+ //
+ // Attempts to sign the provided @object msgParams.
+ // Prefixes the hash before signing as per the new geth behavior.
+ signPersonalMessage (msgParams) {
+ const address = normalizeAddress(msgParams.from)
+ return this.getKeyringForAccount(address)
+ .then((keyring) => {
+ return keyring.signPersonalMessage(address, msgParams.data)
+ })
+ }
+
+ // PRIVATE METHODS
+ //
+ // THESE METHODS ARE ONLY USED INTERNALLY TO THE KEYRING-CONTROLLER
+ // AND SO MAY BE CHANGED MORE LIBERALLY THAN THE ABOVE METHODS.
+
+ // Create First Key Tree
+ // returns @Promise
+ //
+ // Clears the vault,
+ // creates a new one,
+ // creates a random new HD Keyring with 1 account,
+ // makes that account the selected account,
+ // faucets that account on testnet,
+ // puts the current seed words into the state tree.
+ createFirstKeyTree () {
+ this.clearKeyrings()
+ return this.addNewKeyring('HD Key Tree', { numberOfAccounts: 1 })
+ .then((keyring) => {
+ return keyring.getAccounts()
+ })
+ .then((accounts) => {
+ const firstAccount = accounts[0]
+ if (!firstAccount) throw new Error('KeyringController - No account found on keychain.')
+ const hexAccount = normalizeAddress(firstAccount)
+ this.emit('newAccount', hexAccount)
+ this.emit('newVault', hexAccount)
+ return this.setupAccounts(accounts)
+ })
+ .then(this.persistAllKeyrings.bind(this))
+ }
+
+ // Setup Accounts
+ // @array accounts
+ //
+ // returns @Promise(@object account)
+ //
+ // Initializes the provided account array
+ // Gives them numerically incremented nicknames,
+ // and adds them to the accountTracker for regular balance checking.
+ setupAccounts (accounts) {
+ return this.getAccounts()
+ .then((loadedAccounts) => {
+ const arr = accounts || loadedAccounts
+ return Promise.all(arr.map((account) => {
+ return this.getBalanceAndNickname(account)
+ }))
+ })
+ }
+
+ // Get Balance And Nickname
+ // @string account
+ //
+ // returns Promise( @string label )
+ //
+ // Takes an account address and an iterator representing
+ // the current number of named accounts.
+ getBalanceAndNickname (account) {
+ if (!account) {
+ throw new Error('Problem loading account.')
+ }
+ const address = normalizeAddress(account)
+ this.accountTracker.addAccount(address)
+ return this.createNickname(address)
+ }
+
+ // Create Nickname
+ // @string address
+ //
+ // returns Promise( @string label )
+ //
+ // Takes an address, and assigns it an incremented nickname, persisting it.
+ createNickname (address) {
+ const hexAddress = normalizeAddress(address)
+ const identities = this.memStore.getState().identities
+ const currentIdentityCount = Object.keys(identities).length + 1
+ const nicknames = this.store.getState().walletNicknames || {}
+ const existingNickname = nicknames[hexAddress]
+ const name = existingNickname || `Account ${currentIdentityCount}`
+ identities[hexAddress] = {
+ address: hexAddress,
+ name,
+ }
+ this.memStore.updateState({ identities })
+ return this.saveAccountLabel(hexAddress, name)
+ }
+
+ // Persist All Keyrings
+ // @password string
+ //
+ // returns Promise
+ //
+ // Iterates the current `keyrings` array,
+ // serializes each one into a serialized array,
+ // encrypts that array with the provided `password`,
+ // and persists that encrypted string to storage.
+ persistAllKeyrings (password = this.password) {
+ if (typeof password === 'string') {
+ this.password = password
+ this.memStore.updateState({ isUnlocked: true })
+ }
+ return Promise.all(this.keyrings.map((keyring) => {
+ return Promise.all([keyring.type, keyring.serialize()])
+ .then((serializedKeyringArray) => {
+ // Label the output values on each serialized Keyring:
+ return {
+ type: serializedKeyringArray[0],
+ data: serializedKeyringArray[1],
+ }
+ })
+ }))
+ .then((serializedKeyrings) => {
+ return this.encryptor.encrypt(this.password, serializedKeyrings)
+ })
+ .then((encryptedString) => {
+ this.store.updateState({ vault: encryptedString })
+ return true
+ })
+ }
+
+ // Unlock Keyrings
+ // @string password
+ //
+ // returns Promise( @array keyrings )
+ //
+ // Attempts to unlock the persisted encrypted storage,
+ // initializing the persisted keyrings to RAM.
+ unlockKeyrings (password) {
+ const encryptedVault = this.store.getState().vault
+ if (!encryptedVault) {
+ throw new Error('Cannot unlock without a previous vault.')
+ }
+
+ return this.encryptor.decrypt(password, encryptedVault)
+ .then((vault) => {
+ this.password = password
+ this.memStore.updateState({ isUnlocked: true })
+ vault.forEach(this.restoreKeyring.bind(this))
+ return this.keyrings
+ })
+ }
+
+ // Restore Keyring
+ // @object serialized
+ //
+ // returns Promise( @Keyring deserialized )
+ //
+ // Attempts to initialize a new keyring from the provided
+ // serialized payload.
+ //
+ // On success, returns the resulting @Keyring instance.
+ restoreKeyring (serialized) {
+ const { type, data } = serialized
+
+ const Keyring = this.getKeyringClassForType(type)
+ const keyring = new Keyring()
+ return keyring.deserialize(data)
+ .then(() => {
+ return keyring.getAccounts()
+ })
+ .then((accounts) => {
+ return this.setupAccounts(accounts)
+ })
+ .then(() => {
+ this.keyrings.push(keyring)
+ this._updateMemStoreKeyrings()
+ return keyring
+ })
+ }
+
+ // Get Keyring Class For Type
+ // @string type
+ //
+ // Returns @class Keyring
+ //
+ // Searches the current `keyringTypes` array
+ // for a Keyring class whose unique `type` property
+ // matches the provided `type`,
+ // returning it if it exists.
+ getKeyringClassForType (type) {
+ return this.keyringTypes.find(kr => kr.type === type)
+ }
+
+ getKeyringsByType (type) {
+ return this.keyrings.filter((keyring) => keyring.type === type)
+ }
+
+ // Get Accounts
+ // returns Promise( @Array[ @string accounts ] )
+ //
+ // Returns the public addresses of all current accounts
+ // managed by all currently unlocked keyrings.
+ getAccounts () {
+ const keyrings = this.keyrings || []
+ return Promise.all(keyrings.map(kr => kr.getAccounts()))
+ .then((keyringArrays) => {
+ return keyringArrays.reduce((res, arr) => {
+ return res.concat(arr)
+ }, [])
+ })
+ }
+
+ // Get Keyring For Account
+ // @string address
+ //
+ // returns Promise(@Keyring keyring)
+ //
+ // Returns the currently initialized keyring that manages
+ // the specified `address` if one exists.
+ getKeyringForAccount (address) {
+ const hexed = normalizeAddress(address)
+ log.debug(`KeyringController - getKeyringForAccount: ${hexed}`)
+
+ return Promise.all(this.keyrings.map((keyring) => {
+ return Promise.all([
+ keyring,
+ keyring.getAccounts(),
+ ])
+ }))
+ .then(filter((candidate) => {
+ const accounts = candidate[1].map(normalizeAddress)
+ return accounts.includes(hexed)
+ }))
+ .then((winners) => {
+ if (winners && winners.length > 0) {
+ return winners[0][0]
+ } else {
+ throw new Error('No keyring found for the requested account.')
+ }
+ })
+ }
+
+ // Display For Keyring
+ // @Keyring keyring
+ //
+ // returns Promise( @Object { type:String, accounts:Array } )
+ //
+ // Is used for adding the current keyrings to the state object.
+ displayForKeyring (keyring) {
+ return keyring.getAccounts()
+ .then((accounts) => {
+ return {
+ type: keyring.type,
+ accounts: accounts,
+ }
+ })
+ }
+
+ // Add Gas Buffer
+ // @string gas (as hexadecimal value)
+ //
+ // returns @string bufferedGas (as hexadecimal value)
+ //
+ // Adds a healthy buffer of gas to an initial gas estimate.
+ addGasBuffer (gas) {
+ const gasBuffer = new BN('100000', 10)
+ const bnGas = new BN(ethUtil.stripHexPrefix(gas), 16)
+ const correct = bnGas.add(gasBuffer)
+ return ethUtil.addHexPrefix(correct.toString(16))
+ }
+
+ // Clear Keyrings
+ //
+ // Deallocates all currently managed keyrings and accounts.
+ // Used before initializing a new vault.
+ clearKeyrings () {
+ let accounts
+ try {
+ accounts = Object.keys(this.accountTracker.getState())
+ } catch (e) {
+ accounts = []
+ }
+ accounts.forEach((address) => {
+ this.accountTracker.removeAccount(address)
+ })
+
+ // clear keyrings from memory
+ this.keyrings = []
+ this.memStore.updateState({
+ keyrings: [],
+ identities: {},
+ })
+ }
+
+ _updateMemStoreKeyrings () {
+ Promise.all(this.keyrings.map(this.displayForKeyring))
+ .then((keyrings) => {
+ this.memStore.updateState({ keyrings })
+ })
+ }
+
+}
+
+module.exports = KeyringController
diff --git a/app/scripts/lib/account-tracker.js b/app/scripts/lib/account-tracker.js
index bf949597b..e2892b1ce 100644
--- a/app/scripts/lib/account-tracker.js
+++ b/app/scripts/lib/account-tracker.js
@@ -10,15 +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
@@ -33,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 })
}
//
@@ -54,29 +60,31 @@ class EthereumStore extends ObservableStore {
const blockNumber = '0x' + block.number.toString('hex')
this._currentBlockNumber = blockNumber
+ 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)
})
@@ -93,4 +101,4 @@ class EthereumStore extends ObservableStore {
}
-module.exports = EthereumStore
+module.exports = AccountTracker
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/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js
index ebe6b65a8..bbac5ed08 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)
@@ -136,6 +132,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()
@@ -193,7 +190,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))
@@ -276,7 +273,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(),
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": {}
}
diff --git a/package.json b/package.json
index 8526455e7..c3d086194 100644
--- a/package.json
+++ b/package.json
@@ -73,7 +73,8 @@
"eth-phishing-detect": "^1.1.4",
"eth-query": "^2.1.2",
"eth-sig-util": "^1.2.2",
- "eth-token-tracker": "^1.1.3",
+ "eth-simple-keyring": "^1.1.1",
+ "eth-token-tracker": "^1.1.4",
"ethereumjs-tx": "^1.3.0",
"ethereumjs-util": "github:ethereumjs/ethereumjs-util#ac5d0908536b447083ea422b435da27f26615de9",
"ethereumjs-wallet": "^0.6.0",
@@ -193,7 +194,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",
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'
+ }
+
}
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,