Fix order of accounts in `eth_accounts` response (#8342)

* Fix order of accounts in `eth_accounts` response

The accounts returned by `eth_accounts` were in a fixed order - the
order in which the keyring returned them - rather than ordered with the
selected account first. The accounts returned by the `accountsChanged`
event were ordered with the selected account first, but the same order
wasn't used for `eth_accounts`.

We needed to store additional state in order to determine the correct
account order correctly on all dapps. We had only been storing the
current selected account, but since we also need to determine the
primary account per dapp (i.e. the last "selected" account among the
accounts exposed to that dapp), that wasn't enough.

A `lastSelected` property has been added to each identity in the
preferences controller to keep track of the last selected time. This
property is set to the current time (in milliseconds) whenever a new
selection is made. The accounts returned with `accountsChanged` and by
`eth_accounts` are both ordered by this property.

The `updatePermittedAccounts` function was merged with the internal
methods for responding to account selection, to keep things simpler. It
wasn't called externally anyway, so it wasn't needed in the public API.

* Remove caveat update upon change in selected account

The order of accounts in the caveat isn't meaningful, so the caveat
doesn't need to be updated when the accounts get re-ordered.

* Emit event regardless of account order

Now that we're no longer relying upon the caveat for the account order,
we also have no way of knowing if a particular account selection
resulted in a change in order or not. The notification is now emitted
whenever an exposed account is selected - even if the order stayed the
same.

The inpage provider currently caches the account order, so it can be
relied upon to ignore these redundant events. We were already emiting
redundant `accountsChanged` events in some cases anyway.
feature/default_network_editable
Mark Stacey 5 years ago committed by GitHub
parent b2882aa778
commit 63633635ab
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 78
      app/scripts/controllers/permissions/index.js
  2. 20
      app/scripts/controllers/permissions/restrictedMethods.js
  3. 11
      app/scripts/controllers/preferences.js
  4. 1
      app/scripts/metamask-controller.js
  5. 40
      test/unit/app/controllers/metamask-controller-test.js
  6. 24
      test/unit/app/controllers/permissions/mocks.js
  7. 139
      test/unit/app/controllers/permissions/permissions-controller-test.js
  8. 132
      test/unit/app/controllers/permissions/restricted-methods-test.js

@ -47,7 +47,10 @@ export class PermissionsController {
this.notifyAllDomains = notifyAllDomains this.notifyAllDomains = notifyAllDomains
this._showPermissionRequest = showPermissionRequest this._showPermissionRequest = showPermissionRequest
this._restrictedMethods = getRestrictedMethods(this) this._restrictedMethods = getRestrictedMethods({
getKeyringAccounts: this.getKeyringAccounts.bind(this),
getIdentities: this._getIdentities.bind(this),
})
this.permissionsLog = new PermissionsLogController({ this.permissionsLog = new PermissionsLogController({
restrictedMethods: Object.keys(this._restrictedMethods), restrictedMethods: Object.keys(this._restrictedMethods),
store: this.store, store: this.store,
@ -56,6 +59,7 @@ export class PermissionsController {
this.pendingApprovalOrigins = new Set() this.pendingApprovalOrigins = new Set()
this._initializePermissions(restoredPermissions) this._initializePermissions(restoredPermissions)
this._lastSelectedAddress = preferences.getState().selectedAddress this._lastSelectedAddress = preferences.getState().selectedAddress
this.preferences = preferences
preferences.subscribe(async ({ selectedAddress }) => { preferences.subscribe(async ({ selectedAddress }) => {
if (selectedAddress && selectedAddress !== this._lastSelectedAddress) { if (selectedAddress && selectedAddress !== this._lastSelectedAddress) {
@ -139,6 +143,15 @@ export class PermissionsController {
return Boolean(this.permissions.getPermission(origin, permission)) return Boolean(this.permissions.getPermission(origin, permission))
} }
/**
* Gets the identities from the preferences controller store
*
* @returns {Object} identities
*/
_getIdentities () {
return this.preferences.getState().identities
}
/** /**
* Submits a permissions request to rpc-cap. Internal, background use only. * Submits a permissions request to rpc-cap. Internal, background use only.
* *
@ -293,31 +306,6 @@ export class PermissionsController {
} }
} }
/**
* Update the accounts exposed to the given origin. Changes the eth_accounts
* permissions and emits accountsChanged.
* At least one account must be exposed. If no accounts are to be exposed, the
* eth_accounts permissions should be removed completely.
*
* Throws error if the update fails.
*
* @param {string} origin - The origin to change the exposed accounts for.
* @param {string[]} accounts - The new account(s) to expose.
*/
async updatePermittedAccounts (origin, accounts) {
await this.validatePermittedAccounts(accounts)
this.permissions.updateCaveatFor(
origin, 'eth_accounts', CAVEAT_NAMES.exposedAccounts, accounts
)
this.notifyDomain(origin, {
method: NOTIFICATION_NAMES.accountsChanged,
result: accounts,
})
}
/** /**
* Finalizes a permissions request. Throws if request validation fails. * Finalizes a permissions request. Throws if request validation fails.
* Clones the passed-in parameters to prevent inadvertent modification. * Clones the passed-in parameters to prevent inadvertent modification.
@ -434,7 +422,7 @@ export class PermissionsController {
/** /**
* When a new account is selected in the UI, emit accountsChanged to each origin * When a new account is selected in the UI, emit accountsChanged to each origin
* where the account order has changed. * where the selected account is exposed.
* *
* Note: This will emit "false positive" accountsChanged events, but they are * Note: This will emit "false positive" accountsChanged events, but they are
* handled by the inpage provider. * handled by the inpage provider.
@ -461,46 +449,26 @@ export class PermissionsController {
await Promise.all( await Promise.all(
connectedDomains connectedDomains
.map( .map(
(origin) => this._handleConnectedAccountSelected(origin, account) (origin) => this._handleConnectedAccountSelected(origin)
) )
) )
} }
/** /**
* When a new account is selected in the UI for 'origin', emit accountsChanged * When a new account is selected in the UI, emit accountsChanged to 'origin'
* to 'origin' if the selected account is permitted.
* *
* Note: This will emit "false positive" accountsChanged events, but they are * Note: This will emit "false positive" accountsChanged events, but they are
* handled by the inpage provider. * handled by the inpage provider.
* *
* @param {string} origin - The origin. * @param {string} origin - The origin
* @param {string} account - The newly selected account's address.
*/ */
async _handleConnectedAccountSelected (origin, account) { async _handleConnectedAccountSelected (origin) {
const permittedAccounts = await this.getAccounts(origin) const permittedAccounts = await this.getAccounts(origin)
if ( this.notifyDomain(origin, {
typeof origin !== 'string' || !origin.length method: NOTIFICATION_NAMES.accountsChanged,
) { result: permittedAccounts,
throw new Error('Origin should be a non-empty string.') })
}
// do nothing if the account is not permitted for the origin, or
// if it's already first in the array of permitted accounts
if (
!permittedAccounts.includes(account) ||
permittedAccounts[0] === account
) {
return
}
const newPermittedAccounts = [account].concat(
permittedAccounts.filter((_account) => _account !== account)
)
// update permitted accounts to ensure that accounts are returned
// in the same order every time
await this.updatePermittedAccounts(origin, newPermittedAccounts)
} }
/** /**

@ -1,12 +1,28 @@
export default function getRestrictedMethods (permissionsController) { export default function getRestrictedMethods ({ getIdentities, getKeyringAccounts }) {
return { return {
'eth_accounts': { 'eth_accounts': {
description: `View the addresses of the user's chosen accounts.`, description: `View the addresses of the user's chosen accounts.`,
method: (_, res, __, end) => { method: (_, res, __, end) => {
permissionsController.getKeyringAccounts() getKeyringAccounts()
.then((accounts) => { .then((accounts) => {
const identities = getIdentities()
res.result = accounts res.result = accounts
.sort((firstAddress, secondAddress) => {
if (!identities[firstAddress]) {
throw new Error(`Missing identity for address ${firstAddress}`)
} else if (!identities[secondAddress]) {
throw new Error(`Missing identity for address ${secondAddress}`)
} else if (identities[firstAddress].lastSelected === identities[secondAddress].lastSelected) {
return 0
} else if (identities[firstAddress].lastSelected === undefined) {
return 1
} else if (identities[secondAddress].lastSelected === undefined) {
return -1
}
return identities[secondAddress].lastSelected - identities[firstAddress].lastSelected
})
end() end()
}) })
.catch( .catch(

@ -370,8 +370,15 @@ class PreferencesController {
setSelectedAddress (_address) { setSelectedAddress (_address) {
const address = normalizeAddress(_address) const address = normalizeAddress(_address)
this._updateTokens(address) this._updateTokens(address)
this.store.updateState({ selectedAddress: address })
const tokens = this.store.getState().tokens const { identities, tokens } = this.store.getState()
const selectedIdentity = identities[address]
if (!selectedIdentity) {
throw new Error(`Identity for '${address} not found`)
}
selectedIdentity.lastSelected = Date.now()
this.store.updateState({ identities, selectedAddress: address })
return Promise.resolve(tokens) return Promise.resolve(tokens)
} }

@ -576,7 +576,6 @@ export default class MetamaskController extends EventEmitter {
getApprovedAccounts: nodeify(permissionsController.getAccounts.bind(permissionsController)), getApprovedAccounts: nodeify(permissionsController.getAccounts.bind(permissionsController)),
rejectPermissionsRequest: nodeify(permissionsController.rejectPermissionsRequest, permissionsController), rejectPermissionsRequest: nodeify(permissionsController.rejectPermissionsRequest, permissionsController),
removePermissionsFor: permissionsController.removePermissionsFor.bind(permissionsController), removePermissionsFor: permissionsController.removePermissionsFor.bind(permissionsController),
updatePermittedAccounts: nodeify(permissionsController.updatePermittedAccounts, permissionsController),
legacyExposeAccounts: nodeify(permissionsController.legacyExposeAccounts, permissionsController), legacyExposeAccounts: nodeify(permissionsController.legacyExposeAccounts, permissionsController),
getRequestAccountTabIds: (cb) => cb(null, this.getRequestAccountTabIds()), getRequestAccountTabIds: (cb) => cb(null, this.getRequestAccountTabIds()),

@ -243,18 +243,45 @@ describe('MetaMaskController', function () {
return Promise.resolve('0x0') return Promise.resolve('0x0')
}) })
let startTime = Date.now()
await metamaskController.createNewVaultAndRestore('foobar1337', TEST_SEED) await metamaskController.createNewVaultAndRestore('foobar1337', TEST_SEED)
assert.deepEqual(metamaskController.getState().identities, { let endTime = Date.now()
const firstVaultIdentities = cloneDeep(metamaskController.getState().identities)
assert.ok(
(
firstVaultIdentities[TEST_ADDRESS].lastSelected >= startTime &&
firstVaultIdentities[TEST_ADDRESS].lastSelected <= endTime
),
`'${firstVaultIdentities[TEST_ADDRESS].lastSelected}' expected to be between '${startTime}' and '${endTime}'`
)
delete firstVaultIdentities[TEST_ADDRESS].lastSelected
assert.deepEqual(firstVaultIdentities, {
[TEST_ADDRESS]: { address: TEST_ADDRESS, name: DEFAULT_LABEL }, [TEST_ADDRESS]: { address: TEST_ADDRESS, name: DEFAULT_LABEL },
}) })
await metamaskController.preferencesController.setAccountLabel(TEST_ADDRESS, 'Account Foo') await metamaskController.preferencesController.setAccountLabel(TEST_ADDRESS, 'Account Foo')
assert.deepEqual(metamaskController.getState().identities, {
const labelledFirstVaultIdentities = cloneDeep(metamaskController.getState().identities)
delete labelledFirstVaultIdentities[TEST_ADDRESS].lastSelected
assert.deepEqual(labelledFirstVaultIdentities, {
[TEST_ADDRESS]: { address: TEST_ADDRESS, name: 'Account Foo' }, [TEST_ADDRESS]: { address: TEST_ADDRESS, name: 'Account Foo' },
}) })
startTime = Date.now()
await metamaskController.createNewVaultAndRestore('foobar1337', TEST_SEED_ALT) await metamaskController.createNewVaultAndRestore('foobar1337', TEST_SEED_ALT)
assert.deepEqual(metamaskController.getState().identities, { endTime = Date.now()
const secondVaultIdentities = cloneDeep(metamaskController.getState().identities)
assert.ok(
(
secondVaultIdentities[TEST_ADDRESS_ALT].lastSelected >= startTime &&
secondVaultIdentities[TEST_ADDRESS_ALT].lastSelected <= endTime
),
`'${secondVaultIdentities[TEST_ADDRESS_ALT].lastSelected}' expected to be between '${startTime}' and '${endTime}'`
)
delete secondVaultIdentities[TEST_ADDRESS_ALT].lastSelected
assert.deepEqual(secondVaultIdentities, {
[TEST_ADDRESS_ALT]: { address: TEST_ADDRESS_ALT, name: DEFAULT_LABEL }, [TEST_ADDRESS_ALT]: { address: TEST_ADDRESS_ALT, name: DEFAULT_LABEL },
}) })
}) })
@ -271,8 +298,13 @@ describe('MetaMaskController', function () {
return Promise.resolve('0x14ced5122ce0a000') return Promise.resolve('0x14ced5122ce0a000')
}) })
const startTime = Date.now()
await metamaskController.createNewVaultAndRestore('foobar1337', TEST_SEED) await metamaskController.createNewVaultAndRestore('foobar1337', TEST_SEED)
assert.deepEqual(metamaskController.getState().identities, {
const identities = cloneDeep(metamaskController.getState().identities)
assert.ok(identities[TEST_ADDRESS].lastSelected >= startTime && identities[TEST_ADDRESS].lastSelected <= Date.now())
delete identities[TEST_ADDRESS].lastSelected
assert.deepEqual(identities, {
[TEST_ADDRESS]: { address: TEST_ADDRESS, name: DEFAULT_LABEL }, [TEST_ADDRESS]: { address: TEST_ADDRESS, name: DEFAULT_LABEL },
[TEST_ADDRESS_2]: { address: TEST_ADDRESS_2, name: DEFAULT_LABEL_2 }, [TEST_ADDRESS_2]: { address: TEST_ADDRESS_2, name: DEFAULT_LABEL_2 },
}) })

@ -34,6 +34,16 @@ const keyringAccounts = deepFreeze([
const getKeyringAccounts = async () => [ ...keyringAccounts ] const getKeyringAccounts = async () => [ ...keyringAccounts ]
const getIdentities = () => {
return keyringAccounts.reduce(
(identities, address, index) => {
identities[address] = { address, name: `Account ${index}` }
return identities
},
{}
)
}
// perm controller initialization helper // perm controller initialization helper
const getRestrictedMethods = (permController) => { const getRestrictedMethods = (permController) => {
return { return {
@ -73,7 +83,10 @@ export function getPermControllerOpts () {
notifyAllDomains: noop, notifyAllDomains: noop,
preferences: { preferences: {
getState: () => { getState: () => {
return { selectedAddress: keyringAccounts[0] } return {
identities: getIdentities(),
selectedAddress: keyringAccounts[0],
}
}, },
subscribe: noop, subscribe: noop,
}, },
@ -317,14 +330,6 @@ export const getters = deepFreeze({
}, },
}, },
updatePermittedAccounts: {
invalidOrigin: () => {
return {
message: 'No such permission exists for the given domain.',
}
},
},
legacyExposeAccounts: { legacyExposeAccounts: {
badOrigin: () => { badOrigin: () => {
return { return {
@ -584,6 +589,7 @@ export const getters = deepFreeze({
* Objects with immutable mock values. * Objects with immutable mock values.
*/ */
export const constants = deepFreeze({ export const constants = deepFreeze({
ALL_ACCOUNTS: keyringAccounts,
DUMMY_ACCOUNT: '0xabc', DUMMY_ACCOUNT: '0xabc',

@ -33,6 +33,7 @@ const {
} = getters } = getters
const { const {
ALL_ACCOUNTS,
ACCOUNT_ARRAYS, ACCOUNT_ARRAYS,
DUMMY_ACCOUNT, DUMMY_ACCOUNT,
ORIGINS, ORIGINS,
@ -460,115 +461,6 @@ describe('permissions controller', function () {
}) })
}) })
describe('updatePermittedAccounts', function () {
let permController, notifications
beforeEach(function () {
notifications = initNotifications()
permController = initPermController(notifications)
grantPermissions(
permController, ORIGINS.a,
PERMS.finalizedRequests.eth_accounts(ACCOUNT_ARRAYS.a)
)
grantPermissions(
permController, ORIGINS.b,
PERMS.finalizedRequests.eth_accounts(ACCOUNT_ARRAYS.b)
)
})
it('throws on invalid accounts', async function () {
await assert.rejects(
permController.updatePermittedAccounts(ORIGINS.a, {}),
ERRORS.validatePermittedAccounts.invalidParam(),
'should throw on non-array accounts param'
)
await assert.rejects(
permController.updatePermittedAccounts(ORIGINS.a, []),
ERRORS.validatePermittedAccounts.invalidParam(),
'should throw on empty array accounts param'
)
await assert.rejects(
permController.updatePermittedAccounts(ORIGINS.a, [DUMMY_ACCOUNT]),
ERRORS.validatePermittedAccounts.nonKeyringAccount(DUMMY_ACCOUNT),
'should throw on non-keyring account'
)
})
it('throws if origin invalid or lacks eth_accounts permission', async function () {
await assert.rejects(
permController.updatePermittedAccounts(false, ACCOUNT_ARRAYS.a),
ERRORS.updatePermittedAccounts.invalidOrigin(),
'should throw on invalid origin'
)
await assert.rejects(
permController.updatePermittedAccounts(ORIGINS.c, ACCOUNT_ARRAYS.a),
ERRORS.updatePermittedAccounts.invalidOrigin(),
'should throw on origin without eth_accounts permission'
)
})
it('successfully updates permitted accounts', async function () {
await permController.updatePermittedAccounts(ORIGINS.a, ACCOUNT_ARRAYS.b)
await permController.updatePermittedAccounts(ORIGINS.b, ACCOUNT_ARRAYS.c)
let aAccounts = await permController.getAccounts(ORIGINS.a)
let bAccounts = await permController.getAccounts(ORIGINS.b)
assert.deepEqual(
aAccounts, ACCOUNT_ARRAYS.b,
'first origin should have correct accounts'
)
assert.deepEqual(
bAccounts, ACCOUNT_ARRAYS.c,
'first origin should have correct accounts'
)
assert.deepEqual(
notifications[ORIGINS.a][0],
NOTIFICATIONS.newAccounts(ACCOUNT_ARRAYS.b),
'first origin should have correct notification'
)
assert.deepEqual(
notifications[ORIGINS.b][0],
NOTIFICATIONS.newAccounts(ACCOUNT_ARRAYS.c),
'second origin should have correct notification'
)
await permController.updatePermittedAccounts(ORIGINS.a, ACCOUNT_ARRAYS.c)
await permController.updatePermittedAccounts(ORIGINS.b, ACCOUNT_ARRAYS.a)
aAccounts = await permController.getAccounts(ORIGINS.a)
bAccounts = await permController.getAccounts(ORIGINS.b)
assert.deepEqual(
aAccounts, ACCOUNT_ARRAYS.c,
'first origin should have correct accounts'
)
assert.deepEqual(
bAccounts, ACCOUNT_ARRAYS.a,
'first origin should have correct accounts'
)
assert.deepEqual(
notifications[ORIGINS.a][1],
NOTIFICATIONS.newAccounts(ACCOUNT_ARRAYS.c),
'first origin should have correct notification'
)
assert.deepEqual(
notifications[ORIGINS.b][1],
NOTIFICATIONS.newAccounts(ACCOUNT_ARRAYS.a),
'second origin should have correct notification'
)
})
})
describe('finalizePermissionsRequest', function () { describe('finalizePermissionsRequest', function () {
let permController let permController
@ -740,15 +632,24 @@ describe('permissions controller', function () {
describe('preferences state update', function () { describe('preferences state update', function () {
let permController, notifications, preferences let permController, notifications, preferences, identities
beforeEach(function () { beforeEach(function () {
preferences = { identities = ALL_ACCOUNTS.reduce(
getState: () => { (identities, account) => {
return { selectedAddress: DUMMY_ACCOUNT } identities[account] = {}
return identities
}, },
{}
)
preferences = {
getState: sinon.stub(),
subscribe: sinon.stub(), subscribe: sinon.stub(),
} }
preferences.getState.returns({
identities,
selectedAddress: DUMMY_ACCOUNT,
})
notifications = initNotifications() notifications = initNotifications()
permController = new PermissionsController({ permController = new PermissionsController({
...getPermControllerOpts(), ...getPermControllerOpts(),
@ -767,6 +668,7 @@ describe('permissions controller', function () {
}) })
it('should throw if given invalid account', async function () { it('should throw if given invalid account', async function () {
assert(preferences.subscribe.calledOnce) assert(preferences.subscribe.calledOnce)
assert(preferences.subscribe.firstCall.args.length === 1) assert(preferences.subscribe.firstCall.args.length === 1)
const onPreferencesUpdate = preferences.subscribe.firstCall.args[0] const onPreferencesUpdate = preferences.subscribe.firstCall.args[0]
@ -795,7 +697,8 @@ describe('permissions controller', function () {
) )
}) })
it('should do nothing if account already first in array for each connected site', async function () { it('should emit notification if account already first in array for each connected site', async function () {
identities[ACCOUNT_ARRAYS.a[0]] = { lastSelected: 1000 }
assert(preferences.subscribe.calledOnce) assert(preferences.subscribe.calledOnce)
assert(preferences.subscribe.firstCall.args.length === 1) assert(preferences.subscribe.firstCall.args.length === 1)
const onPreferencesUpdate = preferences.subscribe.firstCall.args[0] const onPreferencesUpdate = preferences.subscribe.firstCall.args[0]
@ -803,16 +706,19 @@ describe('permissions controller', function () {
await onPreferencesUpdate({ selectedAddress: ACCOUNT_ARRAYS.a[0] }) await onPreferencesUpdate({ selectedAddress: ACCOUNT_ARRAYS.a[0] })
assert.deepEqual( assert.deepEqual(
notifications[ORIGINS.b], [], notifications[ORIGINS.b],
[NOTIFICATIONS.newAccounts([...ACCOUNT_ARRAYS.a, EXTRA_ACCOUNT])],
'should not have emitted notification' 'should not have emitted notification'
) )
assert.deepEqual( assert.deepEqual(
notifications[ORIGINS.c], [], notifications[ORIGINS.c],
[NOTIFICATIONS.newAccounts(ACCOUNT_ARRAYS.a)],
'should not have emitted notification' 'should not have emitted notification'
) )
}) })
it('should emit notification just for connected domains', async function () { it('should emit notification just for connected domains', async function () {
identities[EXTRA_ACCOUNT] = { lastSelected: 1000 }
assert(preferences.subscribe.calledOnce) assert(preferences.subscribe.calledOnce)
assert(preferences.subscribe.firstCall.args.length === 1) assert(preferences.subscribe.firstCall.args.length === 1)
const onPreferencesUpdate = preferences.subscribe.firstCall.args[0] const onPreferencesUpdate = preferences.subscribe.firstCall.args[0]
@ -831,6 +737,7 @@ describe('permissions controller', function () {
}) })
it('should emit notification for multiple connected domains', async function () { it('should emit notification for multiple connected domains', async function () {
identities[ACCOUNT_ARRAYS.a[1]] = { lastSelected: 1000 }
assert(preferences.subscribe.calledOnce) assert(preferences.subscribe.calledOnce)
assert(preferences.subscribe.firstCall.args.length === 1) assert(preferences.subscribe.firstCall.args.length === 1)
const onPreferencesUpdate = preferences.subscribe.firstCall.args[0] const onPreferencesUpdate = preferences.subscribe.firstCall.args[0]

@ -1,28 +1,25 @@
import { strict as assert } from 'assert' import { strict as assert } from 'assert'
import pify from 'pify'
import getRestrictedMethods import getRestrictedMethods
from '../../../../../app/scripts/controllers/permissions/restrictedMethods' from '../../../../../app/scripts/controllers/permissions/restrictedMethods'
describe('restricted methods', function () { describe('restricted methods', function () {
// this method is tested extensively in other permissions tests
describe('eth_accounts', function () { describe('eth_accounts', function () {
it('should handle getKeyringAccounts error', async function () {
it('handles failure', async function () {
const restrictedMethods = getRestrictedMethods({ const restrictedMethods = getRestrictedMethods({
getKeyringAccounts: async () => { getKeyringAccounts: async () => {
throw new Error('foo') throw new Error('foo')
}, },
}) })
const ethAccountsMethod = pify(restrictedMethods.eth_accounts.method)
const res = {} const res = {}
restrictedMethods.eth_accounts.method(null, res, null, (err) => {
const fooError = new Error('foo') const fooError = new Error('foo')
await assert.rejects(
assert.deepEqual( ethAccountsMethod(null, res, null),
err, fooError, fooError,
'should end with expected error' 'Should reject with expected error'
) )
assert.deepEqual( assert.deepEqual(
@ -30,6 +27,121 @@ describe('restricted methods', function () {
'response should have expected error and no result' 'response should have expected error and no result'
) )
}) })
it('should handle missing identity for first account when sorting', async function () {
const restrictedMethods = getRestrictedMethods({
getIdentities: () => {
return { '0x7e57e2': {} }
},
getKeyringAccounts: async () => ['0x7e57e2', '0x7e57e3'],
})
const ethAccountsMethod = pify(restrictedMethods.eth_accounts.method)
const res = {}
await assert.rejects(ethAccountsMethod(null, res, null))
assert.ok(res.error instanceof Error, 'result should have error')
assert.deepEqual(Object.keys(res), ['error'], 'result should only contain error')
})
it('should handle missing identity for second account when sorting', async function () {
const restrictedMethods = getRestrictedMethods({
getIdentities: () => {
return { '0x7e57e3': {} }
},
getKeyringAccounts: async () => ['0x7e57e2', '0x7e57e3'],
})
const ethAccountsMethod = pify(restrictedMethods.eth_accounts.method)
const res = {}
await assert.rejects(ethAccountsMethod(null, res, null))
assert.ok(res.error instanceof Error, 'result should have error')
assert.deepEqual(Object.keys(res), ['error'], 'result should only contain error')
})
it('should return accounts in keyring order when none are selected', async function () {
const keyringAccounts = ['0x7e57e2', '0x7e57e3', '0x7e57e4', '0x7e57e5']
const restrictedMethods = getRestrictedMethods({
getIdentities: () => {
return keyringAccounts.reduce(
(identities, address) => {
identities[address] = {}
return identities
},
{}
)
},
getKeyringAccounts: async () => [...keyringAccounts],
})
const ethAccountsMethod = pify(restrictedMethods.eth_accounts.method)
const res = {}
await ethAccountsMethod(null, res, null)
assert.deepEqual(res, { result: keyringAccounts }, 'should return accounts in correct order')
})
it('should return accounts in keyring order when all have same last selected time', async function () {
const keyringAccounts = ['0x7e57e2', '0x7e57e3', '0x7e57e4', '0x7e57e5']
const restrictedMethods = getRestrictedMethods({
getIdentities: () => {
return keyringAccounts.reduce(
(identities, address) => {
identities[address] = { lastSelected: 1000 }
return identities
},
{}
)
},
getKeyringAccounts: async () => [...keyringAccounts],
})
const ethAccountsMethod = pify(restrictedMethods.eth_accounts.method)
const res = {}
await ethAccountsMethod(null, res, null)
assert.deepEqual(res, { result: keyringAccounts }, 'should return accounts in correct order')
})
it('should return accounts sorted by last selected (descending)', async function () {
const keyringAccounts = ['0x7e57e2', '0x7e57e3', '0x7e57e4', '0x7e57e5']
const expectedResult = keyringAccounts.slice().reverse()
const restrictedMethods = getRestrictedMethods({
getIdentities: () => {
return keyringAccounts.reduce(
(identities, address, index) => {
identities[address] = { lastSelected: index * 1000 }
return identities
},
{}
)
},
getKeyringAccounts: async () => [...keyringAccounts],
})
const ethAccountsMethod = pify(restrictedMethods.eth_accounts.method)
const res = {}
await ethAccountsMethod(null, res, null)
assert.deepEqual(res, { result: expectedResult }, 'should return accounts in correct order')
})
it('should return accounts sorted by last selected (descending) with unselected accounts last, in keyring order', async function () {
const keyringAccounts = ['0x7e57e2', '0x7e57e3', '0x7e57e4', '0x7e57e5', '0x7e57e6']
const expectedResult = ['0x7e57e4', '0x7e57e2', '0x7e57e3', '0x7e57e5', '0x7e57e6']
const restrictedMethods = getRestrictedMethods({
getIdentities: () => {
return {
'0x7e57e2': { lastSelected: 1000 },
'0x7e57e3': {},
'0x7e57e4': { lastSelected: 2000 },
'0x7e57e5': {},
'0x7e57e6': {},
}
},
getKeyringAccounts: async () => [...keyringAccounts],
})
const ethAccountsMethod = pify(restrictedMethods.eth_accounts.method)
const res = {}
await ethAccountsMethod(null, res, null)
assert.deepEqual(res, { result: expectedResult }, 'should return accounts in correct order')
}) })
}) })
}) })

Loading…
Cancel
Save