From da20a62ae92500b9d2670151dc2e805ed3df07a6 Mon Sep 17 00:00:00 2001 From: Norton Andreev Date: Thu, 22 Feb 2024 16:03:01 +0200 Subject: [PATCH 01/43] web-wallet: Add Create Wallet flow tests Resolves #1443 --- web-wallet/CHANGELOG.md | 3 +- .../(welcome)/setup/create/+page.svelte | 6 +- .../setup/create/MnemonicValidate.svelte | 2 +- .../__tests__/__snapshots__/page.spec.js.snap | 3196 ++++++++++++++++- .../setup/create/__tests__/page.spec.js | 410 ++- .../setup/restore/__tests__/page.spec.js | 8 +- 6 files changed, 3610 insertions(+), 15 deletions(-) diff --git a/web-wallet/CHANGELOG.md b/web-wallet/CHANGELOG.md index c35e73c6e9..d954a0cb78 100644 --- a/web-wallet/CHANGELOG.md +++ b/web-wallet/CHANGELOG.md @@ -8,7 +8,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] ### Added - +- Add Create Wallet flow tests [#1443] - Add visible version, commit hash and build date [#1441] - Add Address validation (Transfer flow) [#1377] @@ -114,6 +114,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 [#1445]: https://github.com/dusk-network/rusk/issues/1445 [#1441]: https://github.com/dusk-network/rusk/issues/1441 [#1460]: https://github.com/dusk-network/rusk/issues/1460 +[#1443]: https://github.com/dusk-network/rusk/issues/1443 [Unreleased]: https://github.com/dusk-network/rusk/tree/master/web-wallet diff --git a/web-wallet/src/routes/(welcome)/setup/create/+page.svelte b/web-wallet/src/routes/(welcome)/setup/create/+page.svelte index 82f89367a9..84abb6ca41 100644 --- a/web-wallet/src/routes/(welcome)/setup/create/+page.svelte +++ b/web-wallet/src/routes/(welcome)/setup/create/+page.svelte @@ -89,7 +89,7 @@ Backup
Mnemonic Phrase - +

Ensure you have backed up the Mnemonic phrase.

diff --git a/web-wallet/src/routes/(welcome)/setup/create/__tests__/__snapshots__/page.spec.js.snap b/web-wallet/src/routes/(welcome)/setup/create/__tests__/__snapshots__/page.spec.js.snap index c3f478b861..21b802eca8 100644 --- a/web-wallet/src/routes/(welcome)/setup/create/__tests__/__snapshots__/page.spec.js.snap +++ b/web-wallet/src/routes/(welcome)/setup/create/__tests__/__snapshots__/page.spec.js.snap @@ -1,6 +1,3200 @@ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html -exports[`Create > should render the Terms of Service step of the Create flow 1`] = ` +exports[`Create > correctly renders the Mnemonic Preview page 1`] = ` +
+
+ + + +

+ Backup +
+ + + Mnemonic Phrase + +

+ +
+
+
+
+ +
+
+
+
+
+
+
+
+
+ + +
+
+ + + + + + +

+ Keep this SAFE +

+ +
+ +
+

+ Please make sure to write your phrase down and save it in a secure location. +

+ +
    +
  1. + cart +
  2. +
  3. + dad +
  4. +
  5. + sail +
  6. +
  7. + wreck +
  8. +
  9. + robot +
  10. +
  11. + grit +
  12. +
  13. + combine +
  14. +
  15. + noble +
  16. +
  17. + rap +
  18. +
  19. + farm +
  20. +
  21. + slide +
  22. +
  23. + sad +
  24. +
+ +
+
+ + + + +
+ + + + + + +
+ + + + + + + + + + + + + + + +
+ + + +
+`; + +exports[`Create > correctly renders the Mnemonic Verification page 1`] = ` +
+
+ + + + + + +

+ Backup +
+ + + Mnemonic Phrase + +

+ +
+
+
+
+ +
+
+
+
+
+
+
+
+
+ + +
+
+ +

+ Verification +

+ +
+ +
+

+ Ensure you have backed up the Mnemonic phrase. +

+ +
+
+ + + +
+ +
    +
  1. + _____ +
  2. +
  3. + _____ +
  4. +
  5. + _____ +
  6. +
  7. + _____ +
  8. +
  9. + _____ +
  10. +
  11. + _____ +
  12. +
  13. + _____ +
  14. +
  15. + _____ +
  16. +
  17. + _____ +
  18. +
  19. + _____ +
  20. +
  21. + _____ +
  22. +
  23. + _____ +
  24. +
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + +
+
+ + + + +
+ + + + + + +
+ + + + + + + + + + + + +
+ + + +
+`; + +exports[`Create > doesn't let the user proceed if they have entered mismatching Mnemonic 1`] = ` +
+
+ + + + + + +

+ Backup +
+ + + Mnemonic Phrase + +

+ +
+
+
+
+ +
+
+
+
+
+
+
+
+
+ + +
+
+ +

+ Verification +

+ +
+ +
+

+ Ensure you have backed up the Mnemonic phrase. +

+ +
+
+ + + +
+ +
    +
  1. + grit +
  2. +
  3. + wreck +
  4. +
  5. + cart +
  6. +
  7. + dad +
  8. +
  9. + rap +
  10. +
  11. + sail +
  12. +
  13. + robot +
  14. +
  15. + combine +
  16. +
  17. + noble +
  18. +
  19. + slide +
  20. +
  21. + sad +
  22. +
  23. + farm +
  24. +
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + +
+ + + + + + +

+ Mnemonic does not match. +

+
+
+
+ + + + +
+ + + + + + +
+ + + + + + + + + + + + +
+ + + +
+`; + +exports[`Create > ensures that the Undo button on the Mnemonic Validate step works as expected 1`] = ` +
+
+ + + + + + +

+ Backup +
+ + + Mnemonic Phrase + +

+ +
+
+
+
+ +
+
+
+
+
+
+
+
+
+ + +
+
+ +

+ Verification +

+ +
+ +
+

+ Ensure you have backed up the Mnemonic phrase. +

+ +
+
+ + + +
+ +
    +
  1. + cart +
  2. +
  3. + dad +
  4. +
  5. + sail +
  6. +
  7. + wreck +
  8. +
  9. + robot +
  10. +
  11. + grit +
  12. +
  13. + combine +
  14. +
  15. + noble +
  16. +
  17. + rap +
  18. +
  19. + farm +
  20. +
  21. + slide +
  22. +
  23. + _____ +
  24. +
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + +
+
+ + + + +
+ + + + + + +
+ + + + + + + + + + + + +
+ + + +
+`; + +exports[`Create > ensures the All Done step renders as expected 1`] = ` +
+
+ + + + + + + + + + + + + + + +

+ Welcome to +
+ + + Dusk + +

+ +
+
+
+
+ +
+
+
+
+
+
+
+
+
+ + +
+
+ +

+ You are all set! +

+ +
+ + All Set +
+ + + + +
+ + + +
+ + + +
+ + + +
+`; + +exports[`Create > ensures the Password step renders as expected 1`] = ` +
+
+ + + + + + + + + +

+ + Password + +
+ + Setup +

+ +
+
+
+
+ +
+
+
+
+
+
+
+
+
+ + +
+
+ + + + + + +

+ Password +

+ +
+ +
+ +
+ + + +
+ + + + + + +

+ Setting a password for your web wallet is optional. Doing so allows you + the convenience of opening your wallet file using a password, but it + weakens the overall security. Not using a password requires you to input + the full mnemonic to open your wallet. +

+
+ + +
+ + + + + + +
+ + + + + + + + + +
+ + + +
+`; + +exports[`Create > ensures the Password step renders as expected 2`] = ` +
+
+ + + + + + + + + +

+ + Password + +
+ + Setup +

+ +
+
+
+
+ +
+
+
+
+
+
+
+
+
+ + +
+
+ + + + + + +

+ Password +

+ +
+ +
+ +
+

+ Please store your password safely. +

+ + + + + +
+ + + + + + Password must be at least 8 characters + +
+
+
+ + + +
+ + + + + + +

+ Setting a password for your web wallet is optional. Doing so allows you + the convenience of opening your wallet file using a password, but it + weakens the overall security. Not using a password requires you to input + the full mnemonic to open your wallet. +

+
+ + +
+ + + + + + +
+ + + + + + + + + +
+ + + +
+`; + +exports[`Create > ensures the Swap To Native Dusk step renders as expected 1`] = ` +
+
+ + + + + + + + + + + + +

+ Swap ERC20 +
+ + to + + Native Dusk + +

+ +
+
+
+
+ +
+
+
+
+
+
+
+
+
+ + +

+ The ERC20 token swap functionality is currently disabled + and will be provided after the launch of the Dusk mainnet. +

+ + + + + +
+ + + + + + +
+ + + + + + +
+ + + +
+`; + +exports[`Create > lets the user proceed if they have entered a matching Mnemonic 1`] = ` +
+
+ + + + + + +

+ Backup +
+ + + Mnemonic Phrase + +

+ +
+
+
+
+ +
+
+
+
+
+
+
+
+
+ + +
+
+ +

+ Verification +

+ +
+ +
+

+ Ensure you have backed up the Mnemonic phrase. +

+ +
+
+ + + +
+ +
    +
  1. + cart +
  2. +
  3. + dad +
  4. +
  5. + sail +
  6. +
  7. + wreck +
  8. +
  9. + robot +
  10. +
  11. + grit +
  12. +
  13. + combine +
  14. +
  15. + noble +
  16. +
  17. + rap +
  18. +
  19. + farm +
  20. +
  21. + slide +
  22. +
  23. + sad +
  24. +
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + +
+
+ + + + +
+ + + + + + +
+ + + + + + + + + + + + +
+ + + +
+`; + +exports[`Create > should render the \`Securely store your seed phrase!\` agreement step after the ToS 1`] = ` +
+
+

+ Backup +
+ + + Mnemonic Phrase + +

+ +
+
+
+
+ +
+
+
+
+
+
+
+
+
+ + +
+
+ +

+ Securely store your seed phrase! +

+ +
+ +
+
+ + + + +
+ + +
+ + + + +
+ +
+
+ + + +
+ + + + + + +

+ To proceed, please check all the relevant boxes. + Dusk will not save and cannot retrieve your passphrase. +

+
+ + +
+ + + + + + + + + Back + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + +
+ + + +
+`; + +exports[`Create > should render the Existing Wallet notice step of the Create flow if there is a userId saved in localStorage 1`] = ` +
+
+
+
+
+ + + + + + +

+ Existing Wallet Detected +

+ +
+ +
+

+ Logging in to a new wallet will overwrite the current local wallet cache, + meaning that when you log in again with the previous mnemonic/account + you will need to wait for the wallet to sync. +

+ +
+ + + Back + + + + + + + + +
+
+
+ + +
+ +
+ + +
+`; + +exports[`Create > should render the Terms of Service step of the Create flow if there is no userId saved in localStorage 1`] = `
{ - afterEach(cleanup); +/** + * @param {HTMLElement} input + * @param {*} value + * @returns {Promise} + */ +const fireInput = (input, value) => fireEvent.input(input, { target: { value } }); + +/** @param {HTMLElement} element */ +function asInput (element) { + // eslint-disable-next-line no-extra-parens + return /** @type {HTMLInputElement} */ (element); +} + +describe("Create", async () => { + const walletGetPsksSpy = vi.spyOn(Wallet.prototype, "getPsks").mockResolvedValue(addresses); + const mnemonic = "cart dad sail wreck robot grit combine noble rap farm slide sad"; + const mnemonicShuffled = ["grit", "wreck", "cart", "dad", "rap", + "sail", "robot", "combine", "noble", "slide", "sad", "farm"]; + const pwd = "passwordpassword"; + const seed = getSeedFromMnemonic(mnemonic); + const userId = (await new Wallet(seed).getPsks())[0]; + const generateMnemonicSpy = vi.spyOn(bip39, "generateMnemonic").mockReturnValue(mnemonic); + const shuffleArraySpy = vi.spyOn(shuffleArray, "shuffleArray").mockReturnValue(mnemonicShuffled); + const getWalletSpy = vi.spyOn(walletService, "getWallet"); + const gotoSpy = vi.spyOn(appNavigation, "goto"); + const settingsResetSpy = vi.spyOn(settingsStore, "reset"); + const clearAndInitSpy = vi.spyOn(walletStore, "clearLocalDataAndInit"); + + afterEach(async () => { + cleanup(); + settingsStore.reset(); + walletGetPsksSpy.mockClear(); + generateMnemonicSpy.mockClear(); + shuffleArraySpy.mockClear(); + clearAndInitSpy.mockClear(); + getWalletSpy.mockClear(); + gotoSpy.mockClear(); + settingsResetSpy.mockClear(); + }); + + afterAll(() => { + walletGetPsksSpy.mockRestore(); + generateMnemonicSpy.mockRestore(); + shuffleArraySpy.mockRestore(); + clearAndInitSpy.mockRestore(); + getWalletSpy.mockRestore(); + gotoSpy.mockRestore(); + settingsResetSpy.mockRestore(); + }); + + it("should render the Existing Wallet notice step of the Create flow if there is a userId saved in localStorage", () => { + settingsStore.update(setKey("userId", userId)); - it("should render the Terms of Service step of the Create flow", () => { const { container } = render(Create); expect(container.firstChild).toMatchSnapshot(); }); + + it("should render the Terms of Service step of the Create flow if there is no userId saved in localStorage", () => { + const { container } = render(Create); + + expect(container.firstChild).toMatchSnapshot(); + }); + + it("should render the `Securely store your seed phrase!` agreement step after the ToS", async () => { + const { container, getByRole } = render(Create); + + const mathRandomSpy = vi.spyOn(Math, "random").mockReturnValue(42); + + await fireEvent.click(getByRole("button", { name: "Accept" })); + + expect(container.firstChild).toMatchSnapshot(); + + mathRandomSpy.mockRestore(); + }); + + it("should not allow the user proceed unless both agreement checks are selected on the `Securely store your seed phrase!` step", async () => { + const { getByRole, getAllByRole } = render(Create); + + await fireEvent.click(getByRole("button", { name: "Accept" })); + + const firstCheckbox = getAllByRole("checkbox")[0]; + const secondCheckbox = getAllByRole("checkbox")[1]; + const nextButton = getByRole("button", { name: "Next" }); + + // Select the first checkbox + await fireEvent.click(firstCheckbox); + + // Ensure Next is disabled + expect(nextButton).toBeDisabled(); + + // Unselect the first checkbox + await fireEvent.click(firstCheckbox); + + // Select the second checkbox + await fireEvent.click(secondCheckbox); + + // Ensure Next is disabled + expect(getByRole("button", { name: "Next" })).toBeDisabled(); + + // Select first checkbox too + await fireEvent.click(firstCheckbox); + + // Ensure Next is enabled + expect(nextButton).toBeEnabled(); + }); + + it("correctly renders the Mnemonic Preview page", async () => { + const { container, getByRole, getAllByRole } = render(Create); + + await fireEvent.click(getByRole("button", { name: "Accept" })); + + await fireEvent.click(getAllByRole("checkbox")[0]); + await fireEvent.click(getAllByRole("checkbox")[1]); + + await fireEvent.click(getByRole("button", { name: "Next" })); + + expect(container.firstChild).toMatchSnapshot(); + }); + + it("correctly renders the Mnemonic Verification page", async () => { + const { container, getByRole, getAllByRole } = render(Create); + + await fireEvent.click(getByRole("button", { name: "Accept" })); + + await fireEvent.click(getAllByRole("checkbox")[0]); + await fireEvent.click(getAllByRole("checkbox")[1]); + + await fireEvent.click(getByRole("button", { name: "Next" })); + await fireEvent.click(getByRole("button", { name: "Next" })); + + expect(container.firstChild).toMatchSnapshot(); + }); + + it("doesn't let the user proceed if they have entered mismatching Mnemonic", async () => { + const { container, getByRole, getAllByRole } = render(Create); + + await fireEvent.click(getByRole("button", { name: "Accept" })); + + await fireEvent.click(getAllByRole("checkbox")[0]); + await fireEvent.click(getAllByRole("checkbox")[1]); + + await fireEvent.click(getByRole("button", { name: "Next" })); + await fireEvent.click(getByRole("button", { name: "Next" })); + + const wordButtonsWrapper = + container.getElementsByClassName("dusk-mnemonic__validate-actions-wrapper")[0]; + + const wordButtons = Array.from(wordButtonsWrapper.children); + + wordButtons.forEach(async button => { + await fireEvent.click(button); + }); + + await tick(); + + expect(container.firstChild).toMatchSnapshot(); + + expect(getByRole("button", { name: "Next" })).toBeDisabled(); + }); + + it("lets the user proceed if they have entered a matching Mnemonic", async () => { + const { container, getByRole, getAllByRole } = render(Create); + + await fireEvent.click(getByRole("button", { name: "Accept" })); + + await fireEvent.click(getAllByRole("checkbox")[0]); + await fireEvent.click(getAllByRole("checkbox")[1]); + + await fireEvent.click(getByRole("button", { name: "Next" })); + await fireEvent.click(getByRole("button", { name: "Next" })); + + const mnemonicSplit = mnemonic.split(" "); + + mnemonicSplit.forEach(async word => { + await fireEvent.click(getByRole("button", { name: word })); + }); + + await tick(); + + expect(container.firstChild).toMatchSnapshot(); + + expect(getByRole("button", { name: "Next" })).toBeEnabled(); + }); + + it("ensures that the Undo button on the Mnemonic Validate step works as expected", async () => { + const { container, getByRole, getAllByRole } = render(Create); + + await fireEvent.click(getByRole("button", { name: "Accept" })); + + await fireEvent.click(getAllByRole("checkbox")[0]); + await fireEvent.click(getAllByRole("checkbox")[1]); + + await fireEvent.click(getByRole("button", { name: "Next" })); + await fireEvent.click(getByRole("button", { name: "Next" })); + + const mnemonicSplit = mnemonic.split(" "); + + mnemonicSplit.forEach(async word => { + await fireEvent.click(getByRole("button", { name: word })); + }); + + await tick(); + + await fireEvent.click(getByRole("button", { name: "Undo" })); + + expect(container.firstChild).toMatchSnapshot(); + + expect(getByRole("button", { name: "Next" })).toBeDisabled(); + }); + + it("ensures the Password step renders as expected", async () => { + const { container, getByRole, getAllByRole } = render(Create); + + await fireEvent.click(getByRole("button", { name: "Accept" })); + + await fireEvent.click(getAllByRole("checkbox")[0]); + await fireEvent.click(getAllByRole("checkbox")[1]); + + await fireEvent.click(getByRole("button", { name: "Next" })); + await fireEvent.click(getByRole("button", { name: "Next" })); + + const mnemonicSplit = mnemonic.split(" "); + + mnemonicSplit.forEach(async word => { + await fireEvent.click(getByRole("button", { name: word })); + }); + + await tick(); + + await fireEvent.click(getByRole("button", { name: "Next" })); + + // Password disabled + expect(container.firstChild).toMatchSnapshot(); + + await fireEvent.click(getByRole("switch")); + + // Password enabled + expect(container.firstChild).toMatchSnapshot(); + }); + + it("ensures the Swap To Native Dusk step renders as expected", async () => { + const { container, getByRole, getAllByRole } = render(Create); + + await fireEvent.click(getByRole("button", { name: "Accept" })); + + await fireEvent.click(getAllByRole("checkbox")[0]); + await fireEvent.click(getAllByRole("checkbox")[1]); + + await fireEvent.click(getByRole("button", { name: "Next" })); + await fireEvent.click(getByRole("button", { name: "Next" })); + + const mnemonicSplit = mnemonic.split(" "); + + mnemonicSplit.forEach(async word => { + await fireEvent.click(getByRole("button", { name: word })); + }); + + await tick(); + + await fireEvent.click(getByRole("button", { name: "Next" })); + await fireEvent.click(getByRole("button", { name: "Next" })); + + expect(container.firstChild).toMatchSnapshot(); + }); + + it("ensures the All Done step renders as expected", async () => { + const { container, getByRole, getAllByRole } = render(Create); + + await fireEvent.click(getByRole("button", { name: "Accept" })); + + await fireEvent.click(getAllByRole("checkbox")[0]); + await fireEvent.click(getAllByRole("checkbox")[1]); + + await fireEvent.click(getByRole("button", { name: "Next" })); + await fireEvent.click(getByRole("button", { name: "Next" })); + + const mnemonicSplit = mnemonic.split(" "); + + mnemonicSplit.forEach(async word => { + await fireEvent.click(getByRole("button", { name: word })); + }); + + await tick(); + + await fireEvent.click(getByRole("button", { name: "Next" })); + await fireEvent.click(getByRole("button", { name: "Next" })); + await fireEvent.click(getByRole("button", { name: "Next" })); + + expect(container.firstChild).toMatchSnapshot(); + }); + + it("should initialize the wallet without setting a password", async () => { + const { getByRole, getAllByRole } = render(Create); + + // ToS step + await fireEvent.click(getByRole("button", { name: "Accept" })); + + // Mnemonic Agreement step + await fireEvent.click(getAllByRole("checkbox")[0]); + await fireEvent.click(getAllByRole("checkbox")[1]); + + await fireEvent.click(getByRole("button", { name: "Next" })); + + // Mnemonic Generate step + await fireEvent.click(getByRole("button", { name: "Next" })); + + // Mnemonic Validate step + const mnemonicSplit = mnemonic.split(" "); + + mnemonicSplit.forEach(async word => { + await fireEvent.click(getByRole("button", { name: word })); + }); + + await tick(); + + await fireEvent.click(getByRole("button", { name: "Next" })); + + // Set Password step + await fireEvent.click(getByRole("button", { name: "Next" })); + expect(loginInfoStorage.get()).toBeNull(); + + // Swap ERC20 to Native Dusk step + await fireEvent.click(getByRole("button", { name: "Next" })); + + // All Done step + await fireEvent.click(getByRole("button", { name: "Next" })); + + await vi.waitUntil(() => gotoSpy.mock.calls.length > 0); + + expect(settingsResetSpy).toHaveBeenCalledTimes(1); + expect(getWalletSpy).toHaveBeenCalledTimes(1); + expect(getWalletSpy).toHaveBeenCalledWith(seed); + expect(clearAndInitSpy).toHaveBeenCalledTimes(1); + expect(clearAndInitSpy).toHaveBeenCalledWith(expect.any(Wallet)); + expect(gotoSpy).toHaveBeenCalledTimes(1); + expect(gotoSpy).toHaveBeenCalledWith("/dashboard"); + }); + + it("should initialize the wallet encrypted mnemonic saved in localStorage", async () => { + const { getByPlaceholderText, getByRole, getAllByRole } = render(Create); + + // ToS step + await fireEvent.click(getByRole("button", { name: "Accept" })); + + // Mnemonic Agreement step + await fireEvent.click(getAllByRole("checkbox")[0]); + await fireEvent.click(getAllByRole("checkbox")[1]); + + await fireEvent.click(getByRole("button", { name: "Next" })); + + // Mnemonic Generate step + await fireEvent.click(getByRole("button", { name: "Next" })); + + // Mnemonic Validate step + const mnemonicSplit = mnemonic.split(" "); + + mnemonicSplit.forEach(async word => { + await fireEvent.click(getByRole("button", { name: word })); + }); + + await tick(); + + await fireEvent.click(getByRole("button", { name: "Next" })); + + // Set Password step + expect(loginInfoStorage.get()).toBeNull(); + + await fireEvent.click(getByRole("switch")); + + await fireInput(asInput(getByPlaceholderText("Set Password")), pwd); + await fireInput(asInput(getByPlaceholderText("Confirm Password")), pwd); + + expect(loginInfoStorage.get()).toBeNull(); + await fireEvent.click(getByRole("button", { name: "Next" })); + await vi.waitFor(() => { + expect(loginInfoStorage.get()).not.toBeNull(); + }); + + // Swap ERC20 to Native Dusk step + await fireEvent.click(getByRole("button", { name: "Next" })); + + // All Done step + await fireEvent.click(getByRole("button", { name: "Next" })); + + await vi.waitUntil(() => gotoSpy.mock.calls.length > 0); + + expect(settingsResetSpy).toHaveBeenCalledTimes(1); + expect(getWalletSpy).toHaveBeenCalledTimes(1); + expect(getWalletSpy).toHaveBeenCalledWith(seed); + expect(clearAndInitSpy).toHaveBeenCalledTimes(1); + expect(clearAndInitSpy).toHaveBeenCalledWith(expect.any(Wallet)); + expect(gotoSpy).toHaveBeenCalledTimes(1); + expect(gotoSpy).toHaveBeenCalledWith("/dashboard"); + }); }); diff --git a/web-wallet/src/routes/(welcome)/setup/restore/__tests__/page.spec.js b/web-wallet/src/routes/(welcome)/setup/restore/__tests__/page.spec.js index 9f80c81d2d..9085d7d47a 100644 --- a/web-wallet/src/routes/(welcome)/setup/restore/__tests__/page.spec.js +++ b/web-wallet/src/routes/(welcome)/setup/restore/__tests__/page.spec.js @@ -70,15 +70,15 @@ describe("Restore", async () => { walletGetPsksSpy.mockRestore(); }); - it("should render the Terms of Service step of the Restore flow if there is no userId saved in localStorage", () => { + it("should render the Existing Wallet notice step of the Restore flow if there is a userId saved in localStorage", () => { + settingsStore.update(setKey("userId", userId)); + const { container } = render(Restore); expect(container.firstChild).toMatchSnapshot(); }); - it("should render the Existing Wallet notice step of the Restore flow if there is a userId saved in localStorage", () => { - settingsStore.update(setKey("userId", userId)); - + it("should render the Terms of Service step of the Restore flow if there is no userId saved in localStorage", () => { const { container } = render(Restore); expect(container.firstChild).toMatchSnapshot(); From 5feed5806aa0d5b7b74ad851f96841170128f9d0 Mon Sep 17 00:00:00 2001 From: goshawk-3 Date: Thu, 22 Feb 2024 16:44:52 +0200 Subject: [PATCH 02/43] node: Add support for lazy-initialization of all services --- node/src/lib.rs | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/node/src/lib.rs b/node/src/lib.rs index 735f0b70a8..02ad2db1f3 100644 --- a/node/src/lib.rs +++ b/node/src/lib.rs @@ -87,6 +87,13 @@ pub trait Network: Send + Sync + 'static { pub trait LongLivedService: Send + Sync { + async fn initialize( + &mut self, + network: Arc>, + database: Arc>, + vm: Arc>, + ) -> anyhow::Result<()>; + async fn execute( &mut self, network: Arc>, @@ -157,6 +164,25 @@ impl Node { self.network.clone() } + pub async fn initialize( + &self, + service_list: &mut Vec>>, + ) -> anyhow::Result<()> { + // Run lazy-initialization of all registered services + for mut service in service_list.into_iter() { + info!("initialize service {}", service.name()); + service + .initialize( + self.network.clone(), + self.database.clone(), + self.vm_handler.clone(), + ) + .await?; + } + + Ok(()) + } + /// Sets up and runs a list of services. pub async fn spawn_all( &self, From ec2ac2c076118f94a4f15c0fc2c59eb56e1ca702 Mon Sep 17 00:00:00 2001 From: goshawk-3 Date: Thu, 22 Feb 2024 16:50:52 +0200 Subject: [PATCH 03/43] node: Impl empty initialize for databroker and mempool --- node/src/databroker.rs | 9 +++++++++ node/src/lib.rs | 4 ++-- node/src/mempool.rs | 9 +++++++++ 3 files changed, 20 insertions(+), 2 deletions(-) diff --git a/node/src/databroker.rs b/node/src/databroker.rs index 5524668f2f..6706523a5e 100644 --- a/node/src/databroker.rs +++ b/node/src/databroker.rs @@ -93,6 +93,15 @@ impl DataBrokerSrv { impl LongLivedService for DataBrokerSrv { + async fn initialize( + &mut self, + _network: Arc>, + _db: Arc>, + _vm: Arc>, + ) -> anyhow::Result<()> { + Ok(()) + } + async fn execute( &mut self, network: Arc>, diff --git a/node/src/lib.rs b/node/src/lib.rs index 02ad2db1f3..ea879515ac 100644 --- a/node/src/lib.rs +++ b/node/src/lib.rs @@ -166,10 +166,10 @@ impl Node { pub async fn initialize( &self, - service_list: &mut Vec>>, + services: &mut [Box>], ) -> anyhow::Result<()> { // Run lazy-initialization of all registered services - for mut service in service_list.into_iter() { + for service in services.iter_mut() { info!("initialize service {}", service.name()); service .initialize( diff --git a/node/src/mempool.rs b/node/src/mempool.rs index bd127cea03..0f85e466dd 100644 --- a/node/src/mempool.rs +++ b/node/src/mempool.rs @@ -55,6 +55,15 @@ impl crate::Filter for TxFilter { impl LongLivedService for MempoolSrv { + async fn initialize( + &mut self, + _network: Arc>, + _db: Arc>, + _vm: Arc>, + ) -> anyhow::Result<()> { + Ok(()) + } + async fn execute( &mut self, network: Arc>, From 8c518df029eb2043b50a48f2e6dce286f578bb8d Mon Sep 17 00:00:00 2001 From: goshawk-3 Date: Thu, 22 Feb 2024 16:53:30 +0200 Subject: [PATCH 04/43] node: Instantiate Acceptor in ChainSrv::Initialize --- node/src/chain.rs | 50 ++++++++++++++++++++++++-------------- node/src/chain/acceptor.rs | 3 +-- 2 files changed, 33 insertions(+), 20 deletions(-) diff --git a/node/src/chain.rs b/node/src/chain.rs index 3932eb846a..f38365b358 100644 --- a/node/src/chain.rs +++ b/node/src/chain.rs @@ -44,38 +44,29 @@ const TOPICS: &[u8] = &[ const ACCEPT_BLOCK_TIMEOUT_SEC: Duration = Duration::from_secs(20); const HEARTBEAT_SEC: Duration = Duration::from_secs(1); -pub struct ChainSrv { +pub struct ChainSrv { /// Inbound wire messages queue inbound: AsyncQueue, keys_path: String, + acceptor: Option>>>, } #[async_trait] impl - LongLivedService for ChainSrv + LongLivedService for ChainSrv { - async fn execute( + async fn initialize( &mut self, network: Arc>, db: Arc>, vm: Arc>, - ) -> anyhow::Result { - // Register routes - LongLivedService::::add_routes( - self, - TOPICS, - self.inbound.clone(), - &network, - ) - .await?; - - // Restore/Load most recent block + ) -> anyhow::Result<()> { let mrb = Self::load_most_recent_block(db.clone(), vm.clone()).await?; let state_hash = mrb.inner().header().state_hash; let provisioners_list = vm.read().await.get_provisioners(state_hash)?; - // Initialize Acceptor and trigger consensus task + // Initialize Acceptor let acc = Acceptor::init_consensus( &self.keys_path, mrb, @@ -85,7 +76,29 @@ impl vm.clone(), ) .await?; - let acc = Arc::new(RwLock::new(acc)); + + self.acceptor = Some(Arc::new(RwLock::new(acc))); + + Ok(()) + } + + async fn execute( + &mut self, + network: Arc>, + _db: Arc>, + _vm: Arc>, + ) -> anyhow::Result { + // Register routes + LongLivedService::::add_routes( + self, + TOPICS, + self.inbound.clone(), + &network, + ) + .await?; + + let acc = self.acceptor.as_mut().unwrap(); + acc.write().await.spawn_task().await; // Start-up FSM instance let mut fsm = SimpleFSM::new(acc.clone(), network.clone()); @@ -200,11 +213,12 @@ impl } } -impl ChainSrv { +impl ChainSrv { pub fn new(keys_path: String) -> Self { Self { inbound: Default::default(), keys_path, + acceptor: None, } } @@ -213,7 +227,7 @@ impl ChainSrv { /// Panics /// /// If register entry is read but block is not found. - async fn load_most_recent_block( + async fn load_most_recent_block( db: Arc>, vm: Arc>, ) -> Result { diff --git a/node/src/chain/acceptor.rs b/node/src/chain/acceptor.rs index 59b6dbfad8..801497af29 100644 --- a/node/src/chain/acceptor.rs +++ b/node/src/chain/acceptor.rs @@ -161,11 +161,10 @@ impl Acceptor { acc.try_revert(RevertTarget::LastFinalizedState).await?; } - acc.spawn_task().await; Ok(acc) } - async fn spawn_task(&self) { + pub async fn spawn_task(&self) { let provisioners_list = self.provisioners_list.read().await.clone(); let base_timeouts = self.adjust_round_base_timeouts().await; From b3fd1fb123e566273be4dec8886026d43933f3d6 Mon Sep 17 00:00:00 2001 From: goshawk-3 Date: Thu, 22 Feb 2024 16:55:12 +0200 Subject: [PATCH 05/43] rusk: Call Node::Initialize --- rusk/src/bin/main.rs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/rusk/src/bin/main.rs b/rusk/src/bin/main.rs index 442781d94c..53200724da 100644 --- a/rusk/src/bin/main.rs +++ b/rusk/src/bin/main.rs @@ -95,7 +95,7 @@ async fn main() -> Result<(), Box> { }; #[cfg(feature = "node")] - let (rusk, node, service_list) = { + let (rusk, node, mut service_list) = { let state_dir = rusk_profile::get_rusk_state_dir()?; info!("Using state from {state_dir:?}"); let rusk = Rusk::new(state_dir)?; @@ -154,6 +154,13 @@ async fn main() -> Result<(), Box> { Some(HttpServer::bind(handler, listen_addr, cert_and_key).await?); } + #[cfg(feature = "node")] + // initialize all registered services + if let Err(err) = node.0.initialize(&mut service_list).await { + tracing::error!("node initialization failed: {}", err); + return Err(err.into()); + } + #[cfg(feature = "node")] // node spawn_all is the entry point if let Err(e) = node.0.spawn_all(service_list).await { From 359fa558f8479d96defd7ec0822def9faa9d16a4 Mon Sep 17 00:00:00 2001 From: goshawk-3 Date: Tue, 27 Feb 2024 11:34:38 +0200 Subject: [PATCH 06/43] node: Add expect --- node/src/chain.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/node/src/chain.rs b/node/src/chain.rs index f38365b358..086db131a4 100644 --- a/node/src/chain.rs +++ b/node/src/chain.rs @@ -97,7 +97,7 @@ impl ) .await?; - let acc = self.acceptor.as_mut().unwrap(); + let acc = self.acceptor.as_mut().expect("initialize is called"); acc.write().await.spawn_task().await; // Start-up FSM instance From a8a3bead18f5467a3b0cc5baabf3ca60854fc229 Mon Sep 17 00:00:00 2001 From: Kieran Hall Date: Wed, 28 Feb 2024 11:39:36 +0100 Subject: [PATCH 07/43] web-wallet: Release 0.3.0 --- web-wallet/CHANGELOG.md | 3 +++ web-wallet/package-lock.json | 4 ++-- web-wallet/package.json | 2 +- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/web-wallet/CHANGELOG.md b/web-wallet/CHANGELOG.md index d954a0cb78..2b32b49f2f 100644 --- a/web-wallet/CHANGELOG.md +++ b/web-wallet/CHANGELOG.md @@ -7,6 +7,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [0.3.0] - 2024-02-28 + ### Added - Add Create Wallet flow tests [#1443] - Add visible version, commit hash and build date [#1441] @@ -118,6 +120,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 [Unreleased]: https://github.com/dusk-network/rusk/tree/master/web-wallet +[0.3.0]: https://github.com/dusk-network/rusk/tree/web-wallet-0.3.0 [0.2.1]: https://github.com/dusk-network/rusk/tree/web-wallet-0.2.1 [0.2.0]: https://github.com/dusk-network/rusk/tree/web-wallet-0.2.0 [0.1.0-beta]: https://github.com/dusk-network/rusk/tree/web-wallet-0.1.0-beta diff --git a/web-wallet/package-lock.json b/web-wallet/package-lock.json index 483cd1eb08..c256a4216e 100644 --- a/web-wallet/package-lock.json +++ b/web-wallet/package-lock.json @@ -1,12 +1,12 @@ { "name": "web-wallet", - "version": "0.2.1", + "version": "0.3.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "web-wallet", - "version": "0.2.1", + "version": "0.3.0", "license": "MPL-2.0", "dependencies": { "@dusk-network/dusk-wallet-js": "0.4.2", diff --git a/web-wallet/package.json b/web-wallet/package.json index 99578ce0a2..b7972a1724 100644 --- a/web-wallet/package.json +++ b/web-wallet/package.json @@ -31,7 +31,7 @@ "typecheck:watch": "svelte-kit sync && svelte-check --tsconfig ./jsconfig.json --watch --fail-on-warnings" }, "type": "module", - "version": "0.2.1", + "version": "0.3.0", "dependencies": { "@dusk-network/dusk-wallet-js": "0.4.2", "@floating-ui/dom": "1.5.3", From 5aeb6623e3744e57bb49a9ffd4cbfc4fa91d0223 Mon Sep 17 00:00:00 2001 From: Norton Andreev Date: Wed, 28 Feb 2024 13:53:54 +0200 Subject: [PATCH 08/43] web-wallet: Remove extraneous block (MnemonicAuth) Resolves #1470 --- web-wallet/CHANGELOG.md | 5 +++++ .../(welcome)/setup/restore/MnemonicAuthenticate.svelte | 6 ++---- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/web-wallet/CHANGELOG.md b/web-wallet/CHANGELOG.md index 2b32b49f2f..343b0b09eb 100644 --- a/web-wallet/CHANGELOG.md +++ b/web-wallet/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Removed + +- Remove extraneous code block in MnemonicAuthenticate [#1470] + ## [0.3.0] - 2024-02-28 ### Added @@ -117,6 +121,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 [#1441]: https://github.com/dusk-network/rusk/issues/1441 [#1460]: https://github.com/dusk-network/rusk/issues/1460 [#1443]: https://github.com/dusk-network/rusk/issues/1443 +[#1470]: https://github.com/dusk-network/rusk/issues/1470 [Unreleased]: https://github.com/dusk-network/rusk/tree/master/web-wallet diff --git a/web-wallet/src/routes/(welcome)/setup/restore/MnemonicAuthenticate.svelte b/web-wallet/src/routes/(welcome)/setup/restore/MnemonicAuthenticate.svelte index 500a67b63d..aaac9be99d 100644 --- a/web-wallet/src/routes/(welcome)/setup/restore/MnemonicAuthenticate.svelte +++ b/web-wallet/src/routes/(welcome)/setup/restore/MnemonicAuthenticate.svelte @@ -14,10 +14,8 @@ export let enteredMnemonicPhrase = []; $: isValid = validateMnemonic(enteredMnemonicPhrase.join(" "), wordlists.english); - $: { - if (enteredMnemonicPhrase.filter(word => word !== "").length === wordLimit && !isValid) { - toast("error", "Invalid mnemonic phrase", mdiAlertOutline); - } + $: if (enteredMnemonicPhrase.filter(word => word !== "").length === wordLimit && !isValid) { + toast("error", "Invalid mnemonic phrase", mdiAlertOutline); } From 98610faae886a9b67ae0a1eea5479f0c2995ec58 Mon Sep 17 00:00:00 2001 From: Norton Andreev Date: Wed, 28 Feb 2024 14:00:46 +0200 Subject: [PATCH 09/43] web-wallet: Fix mismatch between param and typedef Resolves #1471 --- web-wallet/CHANGELOG.md | 5 +++++ .../components/__tests__/OperationResult.spec.js | 16 ++++++++-------- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/web-wallet/CHANGELOG.md b/web-wallet/CHANGELOG.md index 343b0b09eb..0c0f4a70d6 100644 --- a/web-wallet/CHANGELOG.md +++ b/web-wallet/CHANGELOG.md @@ -11,6 +11,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Remove extraneous code block in MnemonicAuthenticate [#1470] +### Fixed + +- Mismatch between param and JSDoc param's type definition (OperationResult.spec.js) [#1471] + ## [0.3.0] - 2024-02-28 ### Added @@ -122,6 +126,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 [#1460]: https://github.com/dusk-network/rusk/issues/1460 [#1443]: https://github.com/dusk-network/rusk/issues/1443 [#1470]: https://github.com/dusk-network/rusk/issues/1470 +[#1471]: https://github.com/dusk-network/rusk/issues/1471 [Unreleased]: https://github.com/dusk-network/rusk/tree/master/web-wallet diff --git a/web-wallet/src/lib/components/__tests__/OperationResult.spec.js b/web-wallet/src/lib/components/__tests__/OperationResult.spec.js index fcad984918..33b66be5d3 100644 --- a/web-wallet/src/lib/components/__tests__/OperationResult.spec.js +++ b/web-wallet/src/lib/components/__tests__/OperationResult.spec.js @@ -11,16 +11,16 @@ import { OperationResult } from ".."; vi.useFakeTimers(); -describe("OperationResult", () => { - const delay = 1000; +/** @type {(delay: number) => Promise} */ +const rejectAfter = delay => new Promise((_, reject) => { + setTimeout(() => reject(new Error("some error")), delay); +}); - /** @type {(delay: number) => Promise} */ - const rejectAfter = ms => new Promise((resolve, reject) => { - setTimeout(() => reject(new Error("some error")), ms); - }); +/** @type {(delay: number) => Promise} */ +const resolveAfter = delay => new Promise(resolve => { setTimeout(resolve, delay); }); - /** @type {(delay: number) => Promise} */ - const resolveAfter = ms => new Promise(resolve => { setTimeout(resolve, ms); }); +describe("OperationResult", () => { + const delay = 1000; const onBeforeLeave = vi.fn(); From 4058b6feb467abbe006d46eeae0326b5cdf3dba4 Mon Sep 17 00:00:00 2001 From: Norton Andreev Date: Wed, 28 Feb 2024 13:48:57 +0200 Subject: [PATCH 10/43] web-wallet: Refactor beta notice as constant Resolves #1469 --- web-wallet/CHANGELOG.md | 7 +++++++ web-wallet/src/routes/(app)/settings/+page.svelte | 13 +++++-------- .../__tests__/__snapshots__/page.spec.js.snap | 12 ++++-------- 3 files changed, 16 insertions(+), 16 deletions(-) diff --git a/web-wallet/CHANGELOG.md b/web-wallet/CHANGELOG.md index 0c0f4a70d6..3506362e50 100644 --- a/web-wallet/CHANGELOG.md +++ b/web-wallet/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Changed + +- Refactor beta notice as constant [#1469] + ### Removed - Remove extraneous code block in MnemonicAuthenticate [#1470] @@ -15,9 +19,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Mismatch between param and JSDoc param's type definition (OperationResult.spec.js) [#1471] + ## [0.3.0] - 2024-02-28 ### Added + - Add Create Wallet flow tests [#1443] - Add visible version, commit hash and build date [#1441] - Add Address validation (Transfer flow) [#1377] @@ -125,6 +131,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 [#1441]: https://github.com/dusk-network/rusk/issues/1441 [#1460]: https://github.com/dusk-network/rusk/issues/1460 [#1443]: https://github.com/dusk-network/rusk/issues/1443 +[#1469]: https://github.com/dusk-network/rusk/issues/1469 [#1470]: https://github.com/dusk-network/rusk/issues/1470 [#1471]: https://github.com/dusk-network/rusk/issues/1471 diff --git a/web-wallet/src/routes/(app)/settings/+page.svelte b/web-wallet/src/routes/(app)/settings/+page.svelte index 59f7902723..95953441f6 100644 --- a/web-wallet/src/routes/(app)/settings/+page.svelte +++ b/web-wallet/src/routes/(app)/settings/+page.svelte @@ -65,6 +65,9 @@ let isValidGas = false; $: ({ isSyncing } = $walletStore); + + // eslint-disable-next-line max-len + const betaNotice = "These functionalities are currently disabled in the beta version of the web wallet and will be enabled in a future update.";
@@ -122,10 +125,7 @@ variant="secondary" />
-

- These functionalities are currently disabled in the beta - version of the web wallet and will be enabled in a future update. -

+

{betaNotice}


@@ -178,10 +178,7 @@ /> -

- This functionality is currently disabled in the beta - version of the web wallet and will be enabled in a future update. -

+

{betaNotice}