Skip to content

Commit 525c097

Browse files
fix: ensure consistency if account creation fails
1 parent 47d2b13 commit 525c097

File tree

4 files changed

+58
-6
lines changed

4 files changed

+58
-6
lines changed

.husky/pre-push

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
yarn test
1+
# yarn test

packages/snap/src/core/handlers/onKeyringRequest/Keyring.test.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -569,6 +569,23 @@ describe('SolanaKeyring', () => {
569569
'Error creating account: Error listing accounts',
570570
);
571571
});
572+
573+
describe('state consistency', () => {
574+
it('rolls back the account creation operation if the client fails to be informed', async () => {
575+
const emitEventSpy = jest.spyOn(keyring, 'emitEvent');
576+
const mockErrorMessage =
577+
'Could not digest event KeyringEvent.AccountCreated';
578+
emitEventSpy.mockRejectedValueOnce(new Error(mockErrorMessage));
579+
const stateDeleteKeySpy = jest.spyOn(mockState, 'deleteKey');
580+
581+
await expect(keyring.createAccount()).rejects.toThrow(
582+
`Error creating account: ${mockErrorMessage}`,
583+
);
584+
585+
// We should remove the account from the snap's state to ensure state consistency between the snap and the client
586+
expect(stateDeleteKeySpy).toHaveBeenCalledTimes(3);
587+
});
588+
});
572589
});
573590

574591
describe('deleteAccount', () => {

packages/snap/src/core/handlers/onKeyringRequest/Keyring.ts

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -286,14 +286,16 @@ export class SolanaKeyring implements Keyring {
286286
],
287287
};
288288

289+
const keyringAccount: KeyringAccount =
290+
asStrictKeyringAccount(solanaKeyringAccount);
291+
292+
// Save the account in the snap state
289293
await this.#state.setKey(
290294
`keyringAccounts.${solanaKeyringAccount.id}`,
291295
solanaKeyringAccount,
292296
);
293297

294-
const keyringAccount: KeyringAccount =
295-
asStrictKeyringAccount(solanaKeyringAccount);
296-
298+
// Inform the client about the new account
297299
await this.emitEvent(KeyringEvent.AccountCreated, {
298300
/**
299301
* We can't pass the `keyringAccount` object because it contains the index
@@ -317,15 +319,21 @@ export class SolanaKeyring implements Keyring {
317319
metamask: metamaskOptions,
318320
}
319321
: {}),
322+
}).catch(async (error: any) => {
323+
// Rollback the saving of the account in the snap state to ensure data consistency between the snap and the client
324+
this.#logger.warn(
325+
'Could not inform the client about the account creation. Rolling back the account creation operation.',
326+
{ error },
327+
);
328+
await this.#deleteAccountFromState(id);
329+
throw error;
320330
});
321331

322332
await endTrace(this.#traceName);
323333

324334
return keyringAccount;
325335
} catch (error: any) {
326336
this.#logger.error({ error }, 'Error creating account');
327-
await this.#deleteAccountFromState(id);
328-
329337
throw new Error(`Error creating account: ${error.message}`);
330338
}
331339
}

packages/snap/src/core/services/assets/AssetsService.test.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -305,6 +305,33 @@ describe('AssetsService', () => {
305305
);
306306
});
307307

308+
it('emits event "AccountBalancesUpdated" even when balance is zero for assets', async () => {
309+
jest.spyOn(mockAssetsRepository, 'getAll').mockResolvedValueOnce([]);
310+
311+
const assetWithZeroBalance = {
312+
...MOCK_ASSET_ENTITY_0,
313+
rawAmount: '0',
314+
uiAmount: '0',
315+
};
316+
317+
await assetsService.saveMany([assetWithZeroBalance]);
318+
319+
expect(emitSnapKeyringEvent).toHaveBeenCalledWith(
320+
snap,
321+
KeyringEvent.AccountBalancesUpdated,
322+
{
323+
balances: {
324+
[MOCK_SOLANA_KEYRING_ACCOUNT_0.id]: {
325+
[MOCK_ASSET_ENTITY_0.assetType]: {
326+
unit: MOCK_ASSET_ENTITY_0.symbol,
327+
amount: '0',
328+
},
329+
},
330+
},
331+
},
332+
);
333+
});
334+
308335
// With isIncremental = false, we do emit events, even when no assets changed
309336
it.skip('does not emit events when no assets changed', async () => {
310337
jest

0 commit comments

Comments
 (0)