Skip to content

Commit

Permalink
Adds the cached-balances controller
Browse files Browse the repository at this point in the history
  • Loading branch information
danjm committed Nov 30, 2018
1 parent cc72d45 commit 85ba0df
Show file tree
Hide file tree
Showing 3 changed files with 229 additions and 0 deletions.
83 changes: 83 additions & 0 deletions app/scripts/controllers/cached-balances.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
const ObservableStore = require('obs-store')
const extend = require('xtend')

/**
* @typedef {Object} CachedBalancesOptions
* @property {Object} accountTracker Account tracker store reference
* @property {Function} getNetwork Returns the current network
* @property {Object} initState Initial state to with which to populate
*/

/**
* Background controller responsible for maintaining
* a cache of account balances in local storage
*/
class CachedBalancesController {
/**
* Creates a new controller instance
*
* @param {CachedBalancesOptions} [opts] Controller configuration parameters
*/
constructor (opts = {}) {
const { accountTracker, getNetwork } = opts

this.accountTracker = accountTracker
this.getNetwork = getNetwork

const initState = extend({
cachedBalances: {},
}, opts.initState)
this.store = new ObservableStore(initState)

this._registerUpdates()
}

/**
* Updates the cachedBalances property for the current network. Cached balances will be updated to those in the passed accounts
* if balances in the passed accounts are truthy.
*
* @param {accounts} [object] Contains the recently updated accounts for the current network
* @returns {Promise<void>} Promises undefined
*/
async updateCachedBalances ({ accounts }) {
const network = await this.getNetwork()
const balancesToCache = await this._generateBalancesToCache(accounts, network)
this.store.updateState({
cachedBalances: balancesToCache,
})
}

_generateBalancesToCache (newAccounts, currentNetwork) {
const { cachedBalances } = this.store.getState()
const currentNetworkBalancesToCache = { ...cachedBalances[currentNetwork] }

Object.keys(newAccounts).forEach(accountID => {
const account = newAccounts[accountID]

if (account.balance) {
currentNetworkBalancesToCache[accountID] = account.balance
}
})
const balancesToCache = {
...cachedBalances,
[currentNetwork]: currentNetworkBalancesToCache,
}

return balancesToCache
}

/**
* Sets up listeners and subscriptions which should trigger an update of cached balances. These updates will
* happen when the current account changes. Which happens on block updates, as well as on network and account
* selections.
*
* @private
*
*/
_registerUpdates () {
const update = this.updateCachedBalances.bind(this)
this.accountTracker.store.subscribe(update)
}
}

module.exports = CachedBalancesController
9 changes: 9 additions & 0 deletions app/scripts/metamask-controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ const ShapeShiftController = require('./controllers/shapeshift')
const AddressBookController = require('./controllers/address-book')
const InfuraController = require('./controllers/infura')
const BlacklistController = require('./controllers/blacklist')
const CachedBalancesController = require('./controllers/cached-balances')
const RecentBlocksController = require('./controllers/recent-blocks')
const MessageManager = require('./lib/message-manager')
const PersonalMessageManager = require('./lib/personal-message-manager')
Expand Down Expand Up @@ -142,6 +143,12 @@ module.exports = class MetamaskController extends EventEmitter {
}
})

this.cachedBalancesController = new CachedBalancesController({
accountTracker: this.accountTracker,
getNetwork: this.networkController.getNetworkState.bind(this.networkController),
initState: initState.CachedBalancesController,
})

// ensure accountTracker updates balances after network change
this.networkController.on('networkDidChange', () => {
this.accountTracker._updateAccounts()
Expand Down Expand Up @@ -241,13 +248,15 @@ module.exports = class MetamaskController extends EventEmitter {
ShapeShiftController: this.shapeshiftController.store,
NetworkController: this.networkController.store,
InfuraController: this.infuraController.store,
CachedBalancesController: this.cachedBalancesController.store,
})

this.memStore = new ComposableObservableStore(null, {
NetworkController: this.networkController.store,
AccountTracker: this.accountTracker.store,
TxController: this.txController.memStore,
BalancesController: this.balancesController.store,
CachedBalancesController: this.cachedBalancesController.store,
TokenRatesController: this.tokenRatesController.store,
MessageManager: this.messageManager.memStore,
PersonalMessageManager: this.personalMessageManager.memStore,
Expand Down
137 changes: 137 additions & 0 deletions test/unit/app/controllers/cached-balances-test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
const assert = require('assert')
const sinon = require('sinon')
const CachedBalancesController = require('../../../../app/scripts/controllers/cached-balances')

describe('CachedBalancesController', () => {
describe('updateCachedBalances', () => {
it('should update the cached balances', async () => {
const controller = new CachedBalancesController({
getNetwork: () => Promise.resolve(17),
accountTracker: {
store: {
subscribe: () => {},
},
},
initState: {
cachedBalances: 'mockCachedBalances',
},
})

controller._generateBalancesToCache = sinon.stub().callsFake(() => Promise.resolve('mockNewCachedBalances'))

await controller.updateCachedBalances({ accounts: 'mockAccounts' })

assert.equal(controller._generateBalancesToCache.callCount, 1)
assert.deepEqual(controller._generateBalancesToCache.args[0], ['mockAccounts', 17])
assert.equal(controller.store.getState().cachedBalances, 'mockNewCachedBalances')
})
})

describe('_generateBalancesToCache', () => {
it('should generate updated account balances where the current network was updated', () => {
const controller = new CachedBalancesController({
accountTracker: {
store: {
subscribe: () => {},
},
},
initState: {
cachedBalances: {
17: {
a: '0x1',
b: '0x2',
c: '0x3',
},
16: {
a: '0xa',
b: '0xb',
c: '0xc',
},
},
},
})

const result = controller._generateBalancesToCache({
a: { balance: '0x4' },
b: { balance: null },
c: { balance: '0x5' },
}, 17)

assert.deepEqual(result, {
17: {
a: '0x4',
b: '0x2',
c: '0x5',
},
16: {
a: '0xa',
b: '0xb',
c: '0xc',
},
})
})

it('should generate updated account balances where the a new network was selected', () => {
const controller = new CachedBalancesController({
accountTracker: {
store: {
subscribe: () => {},
},
},
initState: {
cachedBalances: {
17: {
a: '0x1',
b: '0x2',
c: '0x3',
},
},
},
})

const result = controller._generateBalancesToCache({
a: { balance: '0x4' },
b: { balance: null },
c: { balance: '0x5' },
}, 16)

assert.deepEqual(result, {
17: {
a: '0x1',
b: '0x2',
c: '0x3',
},
16: {
a: '0x4',
c: '0x5',
},
})
})
})

describe('_registerUpdates', () => {
it('should subscribe to the account tracker with the updateCachedBalances method', async () => {
const subscribeSpy = sinon.spy()
const controller = new CachedBalancesController({
getNetwork: () => Promise.resolve(17),
accountTracker: {
store: {
subscribe: subscribeSpy,
},
},
})
subscribeSpy.resetHistory()

const updateCachedBalancesSpy = sinon.spy()
controller.updateCachedBalances = updateCachedBalancesSpy
controller._registerUpdates({ accounts: 'mockAccounts' })

assert.equal(subscribeSpy.callCount, 1)

subscribeSpy.args[0][0]()

assert.equal(updateCachedBalancesSpy.callCount, 1)
})
})

})

0 comments on commit 85ba0df

Please sign in to comment.