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
parent
b2882aa778
commit
63633635ab
@ -1,35 +1,147 @@ |
||||
import { strict as assert } from 'assert' |
||||
import pify from 'pify' |
||||
|
||||
import getRestrictedMethods |
||||
from '../../../../../app/scripts/controllers/permissions/restrictedMethods' |
||||
|
||||
describe('restricted methods', function () { |
||||
|
||||
// this method is tested extensively in other permissions tests
|
||||
describe('eth_accounts', function () { |
||||
|
||||
it('handles failure', async function () { |
||||
it('should handle getKeyringAccounts error', async function () { |
||||
const restrictedMethods = getRestrictedMethods({ |
||||
getKeyringAccounts: async () => { |
||||
throw new Error('foo') |
||||
}, |
||||
}) |
||||
const ethAccountsMethod = pify(restrictedMethods.eth_accounts.method) |
||||
|
||||
const res = {} |
||||
restrictedMethods.eth_accounts.method(null, res, null, (err) => { |
||||
const fooError = new Error('foo') |
||||
await assert.rejects( |
||||
ethAccountsMethod(null, res, null), |
||||
fooError, |
||||
'Should reject with expected error' |
||||
) |
||||
|
||||
const fooError = new Error('foo') |
||||
assert.deepEqual( |
||||
res, { error: fooError }, |
||||
'response should have expected error and no result' |
||||
) |
||||
}) |
||||
|
||||
assert.deepEqual( |
||||
err, fooError, |
||||
'should end with expected error' |
||||
) |
||||
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) |
||||
|
||||
assert.deepEqual( |
||||
res, { error: fooError }, |
||||
'response should have expected error and no result' |
||||
) |
||||
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…
Reference in new issue