Skip to content

Commit 89ebe0d

Browse files
authored
refactor: update account group name uniqueness checks (#6550)
## Explanation <!-- Thanks for your contribution! Take a moment to answer these questions so that reviewers have the information they need to properly understand your changes: * What is the current state of things and why does it need to change? * What is the solution your changes offer and how does it work? * Are there any changes whose purpose might not obvious to those unfamiliar with the domain? * If your primary goal was to update one package but you found you had to update another one along the way, why did you do so? * If you had to upgrade a dependency, why did you do so? --> This PR changes the account groupc uniqueness checks to validate uniqueness only within the same wallet, allowing duplicates between wallets. ## References <!-- Are there any issues that this pull request is tied to? Are there other links that reviewers should consult to understand these changes better? Are there client or consumer pull requests to adopt any breaking changes? For example: * Fixes #12345 * Related to #67890 --> Jira ticket: https://consensyssoftware.atlassian.net/browse/MUL-795 ## Checklist - [x] I've updated the test suite for new or updated code as appropriate - [x] I've updated documentation (JSDoc, Markdown, etc.) for new or updated code as appropriate - [x] I've communicated my changes to consumers by [updating changelogs for packages I've changed](https://github.com/MetaMask/core/tree/main/docs/contributing.md#updating-changelogs), highlighting breaking changes as necessary - [x] I've prepared draft pull requests for clients and consumer packages to resolve any breaking changes
1 parent 2166d54 commit 89ebe0d

File tree

4 files changed

+108
-13
lines changed

4 files changed

+108
-13
lines changed

packages/account-tree-controller/CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10+
### Changed
11+
12+
- Account group name uniqueness validation now scoped to wallet level instead of global ([#6550](https://github.com/MetaMask/core/pull/6550))
13+
- `isAccountGroupNameUnique` now checks for duplicates only within the same wallet, allowing different wallets to have groups with the same name.
14+
- Function now throws an error for non-existent group IDs instead of returning `true`.
15+
- Updated `setAccountGroupName` behavior to allow duplicate names across different wallets.
16+
1017
## [0.13.1]
1118

1219
### Fixed

packages/account-tree-controller/src/AccountTreeController.test.ts

Lines changed: 84 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import type { InternalAccount } from '@metamask/keyring-internal-api';
2424
import type { GetSnap as SnapControllerGetSnap } from '@metamask/snaps-controllers';
2525

2626
import { AccountTreeController } from './AccountTreeController';
27+
import { isAccountGroupNameUnique } from './group';
2728
import { getAccountWalletNameFromKeyringType } from './rules/keyring';
2829
import {
2930
type AccountTreeControllerMessenger,
@@ -1973,7 +1974,7 @@ describe('AccountTreeController', () => {
19731974
}).not.toThrow();
19741975
});
19751976

1976-
it('prevents setting duplicate names across different groups', () => {
1977+
it('allows duplicate names across different wallets', () => {
19771978
const { controller } = setup({
19781979
accounts: [MOCK_HD_ACCOUNT_1, MOCK_HD_ACCOUNT_2],
19791980
keyrings: [MOCK_HD_KEYRING_1, MOCK_HD_KEYRING_2],
@@ -2001,10 +2002,10 @@ describe('AccountTreeController', () => {
20012002
// Set name for first group - should succeed
20022003
controller.setAccountGroupName(groupId1, duplicateName);
20032004

2004-
// Try to set the same name for second group - should throw
2005+
// Set the same name for second group in different wallet - should succeed
20052006
expect(() => {
20062007
controller.setAccountGroupName(groupId2, duplicateName);
2007-
}).toThrow('Account group name already exists');
2008+
}).not.toThrow();
20082009
});
20092010

20102011
it('ensures unique names when generating default names', () => {
@@ -2026,7 +2027,7 @@ describe('AccountTreeController', () => {
20262027
expect(names.every((name) => name.length > 0)).toBe(true);
20272028
});
20282029

2029-
it('prevents duplicate names when comparing trimmed names', () => {
2030+
it('allows duplicate names with different spacing across different wallets', () => {
20302031
const { controller } = setup({
20312032
accounts: [MOCK_HD_ACCOUNT_1, MOCK_HD_ACCOUNT_2],
20322033
keyrings: [MOCK_HD_KEYRING_1, MOCK_HD_KEYRING_2],
@@ -2052,12 +2053,90 @@ describe('AccountTreeController', () => {
20522053
const nameWithSpaces = ' My Group Name ';
20532054
controller.setAccountGroupName(groupId1, nameWithSpaces);
20542055

2055-
// Try to set the same name for second group with different spacing - should throw
2056+
// Set the same name for second group with different spacing in different wallet - should succeed
20562057
const nameWithDifferentSpacing = ' My Group Name ';
20572058
expect(() => {
20582059
controller.setAccountGroupName(groupId2, nameWithDifferentSpacing);
2060+
}).not.toThrow();
2061+
});
2062+
2063+
it('prevents duplicate names within the same wallet', () => {
2064+
// Create two accounts with the same entropy source to ensure they're in the same wallet
2065+
const mockAccount1: Bip44Account<InternalAccount> = {
2066+
...MOCK_HD_ACCOUNT_1,
2067+
id: 'mock-id-1',
2068+
address: '0x123',
2069+
options: {
2070+
entropy: {
2071+
type: KeyringAccountEntropyTypeOption.Mnemonic,
2072+
id: 'mock-keyring-id-1',
2073+
groupIndex: 0,
2074+
derivationPath: '',
2075+
},
2076+
},
2077+
};
2078+
2079+
const mockAccount2: Bip44Account<InternalAccount> = {
2080+
...MOCK_HD_ACCOUNT_2,
2081+
id: 'mock-id-2',
2082+
address: '0x456',
2083+
options: {
2084+
entropy: {
2085+
type: KeyringAccountEntropyTypeOption.Mnemonic,
2086+
id: 'mock-keyring-id-1', // Same entropy ID as account1
2087+
groupIndex: 1, // Different group index to create separate groups
2088+
derivationPath: '',
2089+
},
2090+
},
2091+
};
2092+
2093+
const { controller } = setup({
2094+
accounts: [mockAccount1, mockAccount2],
2095+
keyrings: [MOCK_HD_KEYRING_1],
2096+
});
2097+
2098+
controller.init();
2099+
2100+
const wallets = controller.getAccountWalletObjects();
2101+
expect(wallets).toHaveLength(1);
2102+
2103+
const wallet = wallets[0];
2104+
const groups = Object.values(wallet.groups);
2105+
2106+
expect(groups.length).toBeGreaterThanOrEqual(2);
2107+
2108+
const groupId1 = groups[0].id;
2109+
const groupId2 = groups[1].id;
2110+
const duplicateName = 'Duplicate Group Name';
2111+
2112+
// Set name for first group - should succeed
2113+
controller.setAccountGroupName(groupId1, duplicateName);
2114+
2115+
// Try to set the same name for second group in same wallet - should throw
2116+
expect(() => {
2117+
controller.setAccountGroupName(groupId2, duplicateName);
20592118
}).toThrow('Account group name already exists');
20602119
});
2120+
2121+
it('throws error for non-existent group ID', () => {
2122+
const { controller } = setup({
2123+
accounts: [MOCK_HD_ACCOUNT_1],
2124+
keyrings: [MOCK_HD_KEYRING_1],
2125+
});
2126+
2127+
controller.init();
2128+
2129+
// Test the isAccountGroupNameUnique function directly with a non-existent group ID
2130+
expect(() => {
2131+
isAccountGroupNameUnique(
2132+
controller.state,
2133+
'non-existent-group-id' as AccountGroupId,
2134+
'Some Name',
2135+
);
2136+
}).toThrow(
2137+
'Account group with ID "non-existent-group-id" not found in tree',
2138+
);
2139+
});
20612140
});
20622141

20632142
describe('Fallback Naming', () => {

packages/account-tree-controller/src/AccountTreeController.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -670,11 +670,11 @@ export class AccountTreeController extends BaseController<
670670
}
671671

672672
/**
673-
* Asserts that an account group name is unique across all groups.
673+
* Asserts that an account group name is unique within the same wallet.
674674
*
675675
* @param groupId - The account group ID to exclude from the check.
676676
* @param name - The name to validate for uniqueness.
677-
* @throws Error if the name already exists in another group.
677+
* @throws Error if the name already exists in another group within the same wallet.
678678
*/
679679
#assertAccountGroupNameIsUnique(groupId: AccountGroupId, name: string): void {
680680
if (!isAccountGroupNameUnique(this.state, groupId, name)) {

packages/account-tree-controller/src/group.ts

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -87,12 +87,13 @@ export type AccountGroupObjectOf<GroupType extends AccountGroupType> = Extract<
8787
>['object'];
8888

8989
/**
90-
* Checks if an account group name is unique across all groups.
90+
* Checks if an account group name is unique within the same wallet.
9191
*
9292
* @param state - The account tree controller state.
9393
* @param groupId - The account group ID to exclude from the check.
9494
* @param name - The name to validate for uniqueness.
95-
* @returns True if the name is unique, false otherwise.
95+
* @returns True if the name is unique within the same wallet, false otherwise.
96+
* @throws Error if the group ID does not exist.
9697
*/
9798
export function isAccountGroupNameUnique(
9899
state: AccountTreeControllerState,
@@ -101,13 +102,21 @@ export function isAccountGroupNameUnique(
101102
): boolean {
102103
const trimmedName = name.trim();
103104

105+
// Find the wallet that contains the group being validated
104106
for (const wallet of Object.values(state.accountTree.wallets)) {
105-
for (const group of Object.values(wallet.groups)) {
106-
if (group.id !== groupId && group.metadata.name.trim() === trimmedName) {
107-
return false;
107+
if (wallet.groups[groupId]) {
108+
// Check for duplicates only within this wallet
109+
for (const group of Object.values(wallet.groups)) {
110+
if (
111+
group.id !== groupId &&
112+
group.metadata.name.trim() === trimmedName
113+
) {
114+
return false;
115+
}
108116
}
117+
return true;
109118
}
110119
}
111120

112-
return true;
121+
throw new Error(`Account group with ID "${groupId}" not found in tree`);
113122
}

0 commit comments

Comments
 (0)