From 7e5268369fd8e3011379a11c12df2ed2e14d2c2a Mon Sep 17 00:00:00 2001 From: Angus Bayley Date: Thu, 5 Dec 2024 15:19:58 +0000 Subject: [PATCH 1/4] ci: rename "test web tools" to "build web tools" --- .github/workflows/build-and-test-pr.yml | 8 ++++---- .github/workflows/build-and-test-push.yml | 8 ++++---- ...reusable.yml => build-web-tools-reusable.yml} | 16 ++++++++-------- 3 files changed, 16 insertions(+), 16 deletions(-) rename .github/workflows/{test-web-tools-reusable.yml => build-web-tools-reusable.yml} (89%) diff --git a/.github/workflows/build-and-test-pr.yml b/.github/workflows/build-and-test-pr.yml index 7aea53bd2b01..9c615c5467e3 100644 --- a/.github/workflows/build-and-test-pr.yml +++ b/.github/workflows/build-and-test-pr.yml @@ -72,11 +72,11 @@ jobs: uses: LedgerHQ/ledger-live/.github/workflows/test-design-system-reusable.yml@develop secrets: inherit - test-web-tools: - name: "Test Web Tools" + build-web-tools: + name: "Build Web Tools" needs: determine-affected if: ${{contains(needs.determine-affected.outputs.paths, 'apps/web-tools') && !github.event.pull_request.head.repo.fork}} - uses: LedgerHQ/ledger-live/.github/workflows/test-web-tools-reusable.yml@develop + uses: LedgerHQ/ledger-live/.github/workflows/build-web-tools-reusable.yml@develop secrets: inherit test-cli: @@ -97,7 +97,7 @@ jobs: - test-mobile-e2e - test-libraries - test-design-system - - test-web-tools + - build-web-tools - test-cli runs-on: ubuntu-22.04 if: always() && !cancelled() diff --git a/.github/workflows/build-and-test-push.yml b/.github/workflows/build-and-test-push.yml index d6723b6a511d..2cad88352f97 100644 --- a/.github/workflows/build-and-test-push.yml +++ b/.github/workflows/build-and-test-push.yml @@ -51,9 +51,9 @@ jobs: uses: LedgerHQ/ledger-live/.github/workflows/test-design-system-reusable.yml@develop secrets: inherit - test-web-tools: - name: "Test Web Tools" - uses: LedgerHQ/ledger-live/.github/workflows/test-web-tools-reusable.yml@develop + build-web-tools: + name: "Build Web Tools" + uses: LedgerHQ/ledger-live/.github/workflows/build-web-tools-reusable.yml@develop secrets: inherit test-cli: @@ -72,7 +72,7 @@ jobs: - test-mobile-e2e - test-libraries - test-design-system - - test-web-tools + - build-web-tools - test-cli runs-on: ubuntu-22.04 if: always() && !cancelled() diff --git a/.github/workflows/test-web-tools-reusable.yml b/.github/workflows/build-web-tools-reusable.yml similarity index 89% rename from .github/workflows/test-web-tools-reusable.yml rename to .github/workflows/build-web-tools-reusable.yml index d2b8b604017d..fc92c3c04185 100644 --- a/.github/workflows/test-web-tools-reusable.yml +++ b/.github/workflows/build-web-tools-reusable.yml @@ -1,4 +1,4 @@ -name: "Web Tools Tests" +name: "Build Web Tools" on: workflow_call: @@ -16,8 +16,8 @@ permissions: contents: read jobs: - test-web-tools: - name: "Test web-tools" + build-web-tools: + name: "Build web-tools" env: NODE_OPTIONS: "--max-old-space-size=7168" FORCE_COLOR: 3 @@ -42,7 +42,7 @@ jobs: run: pnpm turbo run build --filter="@ledgerhq/web-tools" --api="http://127.0.0.1:${{ steps.setup-caches.outputs.port }}" --token="${{ secrets.TURBOREPO_SERVER_TOKEN }}" --team="foo" report: - needs: test-web-tools + needs: build-web-tools if: ${{ !cancelled() && github.event_name == 'workflow_dispatch' }} runs-on: ubuntu-22.04 steps: @@ -58,15 +58,15 @@ jobs: const fs = require("fs"); const statuses = { tool: { - pass: ${{ needs.test-web-tools.result == 'success' }}, - status: "${{ needs.test-web-tools.result }}", + pass: ${{ needs.build-web-tools.result == 'success' }}, + status: "${{ needs.build-web-tools.result }}", } }; const summary = `### Common Tools - ${statuses.tool.pass ? "Common Tools are fine" : "Common Tools tests failed"} - - ${statuses.tool.pass ? "✅" : "❌"} **Common Tools* tests* ended with status \`${statuses.tool.status}\` + ${statuses.tool.pass ? "Common Tools are fine" : "Common Tools build failed"} + - ${statuses.tool.pass ? "✅" : "❌"} **Common Tools* build* ended with status \`${statuses.tool.status}\` `; const actions = []; From f11c3a9ab097729e11551a5d1826db5caff02533 Mon Sep 17 00:00:00 2001 From: Kevin Le Seigle Date: Thu, 5 Dec 2024 15:13:40 +0100 Subject: [PATCH 2/4] fix(lld): use a debounce on memotag field --- .changeset/seven-ravens-rest.md | 5 +++++ .../MemoTag/__tests__/MemoTagField.test.tsx | 12 ++++++++++- .../MemoTag/components/MemoTagField.tsx | 21 +++++++++++++++---- .../renderer/families/casper/MemoField.tsx | 2 +- .../families/xrp/SendRecipientFields.tsx | 3 ++- 5 files changed, 36 insertions(+), 7 deletions(-) create mode 100644 .changeset/seven-ravens-rest.md diff --git a/.changeset/seven-ravens-rest.md b/.changeset/seven-ravens-rest.md new file mode 100644 index 000000000000..8a4f89f57f08 --- /dev/null +++ b/.changeset/seven-ravens-rest.md @@ -0,0 +1,5 @@ +--- +"ledger-live-desktop": minor +--- + +LLD: LIVE-15143 use a debounce on the memotag field diff --git a/apps/ledger-live-desktop/src/newArch/features/MemoTag/__tests__/MemoTagField.test.tsx b/apps/ledger-live-desktop/src/newArch/features/MemoTag/__tests__/MemoTagField.test.tsx index 8e75955c2f66..6dc92249e143 100644 --- a/apps/ledger-live-desktop/src/newArch/features/MemoTag/__tests__/MemoTagField.test.tsx +++ b/apps/ledger-live-desktop/src/newArch/features/MemoTag/__tests__/MemoTagField.test.tsx @@ -7,6 +7,14 @@ import { render, screen, fireEvent } from "tests/testUtils"; import MemoTagField from "../components/MemoTagField"; describe("MemoTagField", () => { + beforeAll(() => { + jest.useFakeTimers(); + }); + + afterAll(() => { + jest.useRealTimers(); + }); + it("renders MemoTagField with label and text field", () => { render(); expect(screen.getByText(/Tag \/ Memo/gi)).toBeInTheDocument(); @@ -19,12 +27,14 @@ describe("MemoTagField", () => { expect(screen.queryByText(/Tag \/ Memo/gi)).not.toBeInTheDocument(); }); - it("should call onChange when input value changes", () => { + it("should call onChange when input value changes with a debounce", () => { const handleChange = jest.fn(); render(); fireEvent.change(screen.getByPlaceholderText(/Enter Tag \/ Memo/gi), { target: { value: "new memo" }, }); + expect(handleChange).not.toHaveBeenCalled(); + jest.runAllTimers(); expect(handleChange).toHaveBeenCalledTimes(1); }); diff --git a/apps/ledger-live-desktop/src/newArch/features/MemoTag/components/MemoTagField.tsx b/apps/ledger-live-desktop/src/newArch/features/MemoTag/components/MemoTagField.tsx index a6be608741a6..c54b41585285 100644 --- a/apps/ledger-live-desktop/src/newArch/features/MemoTag/components/MemoTagField.tsx +++ b/apps/ledger-live-desktop/src/newArch/features/MemoTag/components/MemoTagField.tsx @@ -1,12 +1,12 @@ -import React from "react"; +import React, { useState, useEffect } from "react"; import Input, { Props as InputBaseProps } from "~/renderer/components/Input"; +import { useDebounce } from "@ledgerhq/live-common//hooks/useDebounce"; import Label from "~/renderer/components/Label"; import Box from "~/renderer/components/Box"; import { useTranslation } from "react-i18next"; import { Flex, Text, Tooltip } from "@ledgerhq/react-ui"; import styled from "styled-components"; import InfoCircle from "~/renderer/icons/InfoCircle"; - const TooltipContainer = styled(Box)` background-color: ${({ theme }) => theme.colors.palette.neutral.c100}; padding: 10px; @@ -30,6 +30,7 @@ type MemoTagFieldProps = InputBaseProps & { placeholder?: string; label?: string; tooltipText?: string; + validationHandler?: (newValue: string) => string; }; const MemoTagField = ({ @@ -44,8 +45,20 @@ const MemoTagField = ({ placeholder, label, tooltipText, + validationHandler, }: MemoTagFieldProps) => { const { t } = useTranslation(); + const [memoValue, setMemoValue] = useState(value); + const debouncedMemoValue = useDebounce(memoValue, 300); + + useEffect(() => { + if (debouncedMemoValue !== value) onChange?.(debouncedMemoValue || ""); + }, [debouncedMemoValue, onChange, value]); + + const handleChange = (newValue: string) => { + setMemoValue(validationHandler ? validationHandler(newValue) : newValue); + }; + return ( {showLabel && ( @@ -68,10 +81,10 @@ const MemoTagField = ({ {CaracterCountComponent && } { - value = value.replace(/\D/g, ""); if (value !== "") onChange(bridge.updateTransaction(transaction, { transferId: value })); else onChange(bridge.updateTransaction(transaction, { transferId: undefined })); }, @@ -32,6 +31,7 @@ const MemoField = ({ onChange, account, transaction, status, autoFocus }: MemoTa onChange={onTransferIdFieldChange} spellCheck="false" autoFocus={autoFocus} + validationHandler={newValue => newValue.replace(/\D/g, "")} /> ); }; diff --git a/apps/ledger-live-desktop/src/renderer/families/xrp/SendRecipientFields.tsx b/apps/ledger-live-desktop/src/renderer/families/xrp/SendRecipientFields.tsx index 14b073addb19..8c9a3e56239d 100644 --- a/apps/ledger-live-desktop/src/renderer/families/xrp/SendRecipientFields.tsx +++ b/apps/ledger-live-desktop/src/renderer/families/xrp/SendRecipientFields.tsx @@ -15,7 +15,7 @@ const TagField = ({ onChange, account, transaction, autoFocus }: Props) => { const onChangeTag = useCallback( (str: string) => { const bridge = getAccountBridge(account); - const tag = BigNumber(str.replace(/[^0-9]/g, "")); + const tag = BigNumber(str); const patch = { tag: !tag.isNaN() && @@ -37,6 +37,7 @@ const TagField = ({ onChange, account, transaction, autoFocus }: Props) => { value={String(transaction.tag || "")} onChange={onChangeTag} autoFocus={autoFocus} + validationHandler={str => str.replace(/[^0-9]/g, "")} /> ); }; From dfe2636fd414e0d90a89962d6477b17c3838adc8 Mon Sep 17 00:00:00 2001 From: Theophile Sandoz Date: Fri, 6 Dec 2024 13:10:13 +0100 Subject: [PATCH 3/4] =?UTF-8?q?fix(llm):=20=F0=9F=90=9B=20fix=20long=20Mem?= =?UTF-8?q?o=20Tag=20issue=20on=20Stacks=20(#8604)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix(llm): fix long Memo Tag issue in Stacks * chore: update change log * chore: update unimported * fix(llm): use `truncateUtf8` instead of `Array.slice` * chore(llm): use `maxLength` instead of `Array.slice` on Algorand * fix(llm): validate the memo bytes length instead of the js string length --- .changeset/rude-bats-fold.md | 7 +++++++ .../src/families/algorand/MemoTagInput.tsx | 2 +- .../src/families/stacks/MemoTagInput.tsx | 9 +++++++++ .../coin-stacks/src/bridge/getTransactionStatus.ts | 5 ++++- libs/coin-modules/coin-stacks/src/contants.ts | 1 + libs/ledger-live-common/.unimportedrc.json | 1 + libs/ledger-live-common/src/families/stacks/constants.ts | 2 ++ 7 files changed, 25 insertions(+), 2 deletions(-) create mode 100644 .changeset/rude-bats-fold.md create mode 100644 libs/coin-modules/coin-stacks/src/contants.ts create mode 100644 libs/ledger-live-common/src/families/stacks/constants.ts diff --git a/.changeset/rude-bats-fold.md b/.changeset/rude-bats-fold.md new file mode 100644 index 000000000000..027392d9dd24 --- /dev/null +++ b/.changeset/rude-bats-fold.md @@ -0,0 +1,7 @@ +--- +"@ledgerhq/coin-stacks": minor +"live-mobile": minor +"@ledgerhq/live-common": minor +--- + +Truncate Stacks memos in the input to prevent the transaction validation from failing diff --git a/apps/ledger-live-mobile/src/families/algorand/MemoTagInput.tsx b/apps/ledger-live-mobile/src/families/algorand/MemoTagInput.tsx index c21f44e967c5..ce2356498c2c 100644 --- a/apps/ledger-live-mobile/src/families/algorand/MemoTagInput.tsx +++ b/apps/ledger-live-mobile/src/families/algorand/MemoTagInput.tsx @@ -8,7 +8,7 @@ import { GenericMemoTagInput } from "LLM/features/MemoTag/components/GenericMemo export default (props: MemoTagInputProps) => ( text.slice(0, ALGORAND_MAX_MEMO_SIZE)} + maxLength={ALGORAND_MAX_MEMO_SIZE} valueToTxPatch={value => tx => ({ ...tx, memo: value || undefined })} /> ); diff --git a/apps/ledger-live-mobile/src/families/stacks/MemoTagInput.tsx b/apps/ledger-live-mobile/src/families/stacks/MemoTagInput.tsx index f15b17279ef1..0b667ab53b87 100644 --- a/apps/ledger-live-mobile/src/families/stacks/MemoTagInput.tsx +++ b/apps/ledger-live-mobile/src/families/stacks/MemoTagInput.tsx @@ -1,12 +1,21 @@ import React from "react"; +import { STACKS_MAX_MEMO_SIZE } from "@ledgerhq/live-common/families/stacks/constants"; import type { Transaction as StacksTransaction } from "@ledgerhq/live-common/families/stacks/types"; import type { MemoTagInputProps } from "LLM/features/MemoTag/types"; import { GenericMemoTagInput } from "LLM/features/MemoTag/components/GenericMemoTagInput"; +import { truncateUtf8 } from "LLM/utils/truncateUtf8"; + +// `TextInputProps.maxLength` can not be used here because it counts the length of the string instead of the byte size. +// E.g. +// Javascript will evaluate a 16👍 emojis string as 34 characters. +// While this string is in fact encoded in 68B in UTF8 which is well above STX limit. +// `truncateUtf8` will truncate the string correctly, which is 8👍 with 2 characters to spare. export default (props: MemoTagInputProps) => ( truncateUtf8(text, STACKS_MAX_MEMO_SIZE)} valueToTxPatch={value => tx => ({ ...tx, memo: value || undefined })} /> ); diff --git a/libs/coin-modules/coin-stacks/src/bridge/getTransactionStatus.ts b/libs/coin-modules/coin-stacks/src/bridge/getTransactionStatus.ts index 33fcfbee31a1..94812a0f53fc 100644 --- a/libs/coin-modules/coin-stacks/src/bridge/getTransactionStatus.ts +++ b/libs/coin-modules/coin-stacks/src/bridge/getTransactionStatus.ts @@ -8,6 +8,7 @@ import { } from "@ledgerhq/errors"; import { AccountBridge } from "@ledgerhq/types-live"; import BigNumber from "bignumber.js"; +import { STACKS_MAX_MEMO_SIZE } from "../contants"; import { StacksMemoTooLong } from "../errors"; import { Transaction, TransactionStatus } from "../types"; import { validateAddress } from "./utils/addresses"; @@ -44,7 +45,9 @@ export const getTransactionStatus: AccountBridge["getTransactionSta if (amount.lte(0)) errors.amount = new AmountRequired(); if (totalSpent.gt(spendableBalance)) errors.amount = new NotEnoughBalance(); - if (memo && memo.length > 34) errors.transaction = new StacksMemoTooLong(); + + const memoBytesLength = Buffer.from(memo ?? "", "utf-8").byteLength; + if (memoBytesLength > STACKS_MAX_MEMO_SIZE) errors.transaction = new StacksMemoTooLong(); return { errors, diff --git a/libs/coin-modules/coin-stacks/src/contants.ts b/libs/coin-modules/coin-stacks/src/contants.ts new file mode 100644 index 000000000000..8566bed0e242 --- /dev/null +++ b/libs/coin-modules/coin-stacks/src/contants.ts @@ -0,0 +1 @@ +export const STACKS_MAX_MEMO_SIZE = 34; diff --git a/libs/ledger-live-common/.unimportedrc.json b/libs/ledger-live-common/.unimportedrc.json index 27a029333edb..e27e298e85b7 100644 --- a/libs/ledger-live-common/.unimportedrc.json +++ b/libs/ledger-live-common/.unimportedrc.json @@ -124,6 +124,7 @@ "src/families/solana/react.ts", "src/families/solana/staking.ts", "src/families/solana/types.ts", + "src/families/stacks/constants.ts", "src/families/stacks/deviceTransactionConfig.ts", "src/families/stacks/types.ts", "src/families/stellar/types.ts", diff --git a/libs/ledger-live-common/src/families/stacks/constants.ts b/libs/ledger-live-common/src/families/stacks/constants.ts new file mode 100644 index 000000000000..57651b257602 --- /dev/null +++ b/libs/ledger-live-common/src/families/stacks/constants.ts @@ -0,0 +1,2 @@ +// Encapsulate for LLD & LLM +export * from "@ledgerhq/coin-stacks/contants"; From 62459c86650378f8ee4b18c022924d1587b01b1c Mon Sep 17 00:00:00 2001 From: Abdurrahman SASTIM Date: Fri, 6 Dec 2024 13:54:23 +0100 Subject: [PATCH 4/4] test: change default validator --- .../ledger-live-desktop/tests/specs/speculos/delegate.spec.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/ledger-live-desktop/tests/specs/speculos/delegate.spec.ts b/apps/ledger-live-desktop/tests/specs/speculos/delegate.spec.ts index 215394feb876..214660444c95 100644 --- a/apps/ledger-live-desktop/tests/specs/speculos/delegate.spec.ts +++ b/apps/ledger-live-desktop/tests/specs/speculos/delegate.spec.ts @@ -13,7 +13,7 @@ const e2eDelegationAccounts = [ xrayTicket: "B2CQA-2740, B2CQA-2770", }, { - delegate: new Delegate(Account.SOL_1, "0.001", "Ledger by Figment"), + delegate: new Delegate(Account.SOL_1, "0.001", "Ledger by Chorus One"), xrayTicket: "B2CQA-2742", }, { @@ -28,7 +28,7 @@ const validators = [ xrayTicket: "B2CQA-2731, B2CQA-2763", }, { - delegate: new Delegate(Account.SOL_2, "0.001", "Ledger by Figment"), + delegate: new Delegate(Account.SOL_2, "0.001", "Ledger by Chorus One"), xrayTicket: "B2CQA-2730, B2CQA-2764", }, {