Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/assets-controllers/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Fixed

- `MultichainAssetsController`: fungible `token:` assets from automatic detection are no longer added when Blockaid bulk scan fails, returns empty, or omits that address (previously fail open); an explicit non-malicious per-token result from `PhishingController:bulkScanTokens` is now required before add. ([#8400](https://github.com/MetaMask/core/pull/8400))
- Fix `AccountTrackerController` wiping existing balances on other chains when syncing accounts for a chain that has no state entry yet ([#8505](https://github.com/MetaMask/core/pull/8505))

## [104.0.0]

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -403,6 +403,72 @@ describe('AccountTrackerController', () => {
);
});

it('should not wipe existing balances when syncing accounts and the selected chain has no state entry', async () => {
const networkClientId = 'networkClientId1';

mockedGetTokenBalancesForMultipleAddresses.mockResolvedValueOnce({
tokenBalances: {
'0x0000000000000000000000000000000000000000': {},
},
stakedBalances: {},
});

await withController(
{
options: {
state: {
accountsByChainId: {
'0xe705': {
[CHECKSUM_ADDRESS_1]: {
balance: '0xabc',
stakedBalance: '0x5',
},
},
},
},
},
isMultiAccountBalancesEnabled: true,
selectedAccount: ACCOUNT_1,
listAccounts: [ACCOUNT_1],
networkClientById: {
[networkClientId]: buildCustomNetworkClientConfiguration({
chainId: '0x999',
}),
},
},
async ({ controller, refresh }) => {
// Verify initial state has the balance we expect to preserve
expect(
controller.state.accountsByChainId['0xe705'][CHECKSUM_ADDRESS_1],
).toStrictEqual({
balance: '0xabc',
stakedBalance: '0x5',
});

// Refresh for a new chain. The selected network (mainnet / 0x1) is
// NOT in accountsByChainId, so #syncAccounts sees an empty "existing"
// set. Without the fix this would overwrite every address on every
// chain with { balance: '0x0' }, wiping both balance and stakedBalance.
await refresh(['networkClientId1'], true);

// Existing balances must be preserved
expect(
controller.state.accountsByChainId['0xe705'][CHECKSUM_ADDRESS_1],
).toStrictEqual({
balance: '0xabc',
stakedBalance: '0x5',
});

// New chain should have been initialised with a zero balance
expect(
controller.state.accountsByChainId['0x999'][CHECKSUM_ADDRESS_1],
).toStrictEqual({
balance: '0x0',
});
},
);
});

it('sets isActive to true when keyring is unlocked', async () => {
await withController(
{
Expand Down
8 changes: 5 additions & 3 deletions packages/assets-controllers/src/AccountTrackerController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -470,9 +470,11 @@ export class AccountTrackerController extends StaticIntervalPollingController<Ac
);
Object.keys(accountsByChainId).forEach((chainId) => {
newAddresses.forEach((address) => {
accountsByChainId[chainId][address] = {
balance: '0x0',
};
if (!accountsByChainId[chainId][address]) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice catch

accountsByChainId[chainId][address] = {
balance: '0x0',
};
}
});
});

Expand Down
Loading