From ba7a41bd0bf8975d37361d0afb2d8557b70d3c1c Mon Sep 17 00:00:00 2001 From: hzheng-ledger Date: Tue, 15 Aug 2023 13:30:20 +0200 Subject: [PATCH] chore: add evm mock test --- .../AddAccounts/steps/StepChooseCurrency.tsx | 12 +- .../AddAccounts/steps/StepConnectDevice.tsx | 3 + .../tests/specs/accounts/account.spec.ts | 2 +- .../screens/AddAccounts/01-SelectCrypto.tsx | 12 +- libs/coin-framework/src/mocks/account.ts | 6 + libs/coin-framework/src/mocks/helpers.ts | 2 +- .../src/families/evm/bridge/mock.ts | 114 ++++++++++++++++++ .../src/generated/bridge/mock.ts | 2 + 8 files changed, 141 insertions(+), 12 deletions(-) create mode 100644 libs/ledger-live-common/src/families/evm/bridge/mock.ts diff --git a/apps/ledger-live-desktop/src/renderer/modals/AddAccounts/steps/StepChooseCurrency.tsx b/apps/ledger-live-desktop/src/renderer/modals/AddAccounts/steps/StepChooseCurrency.tsx index ca35690ee626..fba929cbba13 100644 --- a/apps/ledger-live-desktop/src/renderer/modals/AddAccounts/steps/StepChooseCurrency.tsx +++ b/apps/ledger-live-desktop/src/renderer/modals/AddAccounts/steps/StepChooseCurrency.tsx @@ -62,7 +62,7 @@ const StepChooseCurrency = ({ currency, setCurrency }: StepProps) => { const base = useFeature("currencyBase"); const baseGoerli = useFeature("currencyBaseGoerli"); const klaytn = useFeature("currencyKlaytn"); - + const mock = useEnv("MOCK"); const featureFlaggedCurrencies = useMemo( () => ({ // Keys in this list must match an existing currency.id @@ -141,11 +141,13 @@ const StepChooseCurrency = ({ currency, setCurrency }: StepProps) => { const currencies = (listSupportedCurrencies() as CryptoOrTokenCurrency[]).concat( listSupportedTokens(), ); - const deactivatedCurrencies = Object.entries(featureFlaggedCurrencies) - .filter(([, feature]) => !feature?.enabled) - .map(([name]) => name); + const deactivatedCurrencies = mock + ? [] + : Object.entries(featureFlaggedCurrencies) + .filter(([, feature]) => !feature?.enabled) + .map(([name]) => name); return currencies.filter(c => !deactivatedCurrencies.includes(c.id)); - }, [featureFlaggedCurrencies]); + }, [featureFlaggedCurrencies, mock]); const url = currency && currency.type === "TokenCurrency" ? supportLinkByTokenType[currency.tokenType as keyof typeof supportLinkByTokenType] diff --git a/apps/ledger-live-desktop/src/renderer/modals/AddAccounts/steps/StepConnectDevice.tsx b/apps/ledger-live-desktop/src/renderer/modals/AddAccounts/steps/StepConnectDevice.tsx index 61ab03cc47c9..6e67d48edfbf 100644 --- a/apps/ledger-live-desktop/src/renderer/modals/AddAccounts/steps/StepConnectDevice.tsx +++ b/apps/ledger-live-desktop/src/renderer/modals/AddAccounts/steps/StepConnectDevice.tsx @@ -8,6 +8,9 @@ import { StepProps } from ".."; import { getEnv } from "@ledgerhq/live-common/env"; import { mockedEventEmitter } from "~/renderer/components/debug/DebugMock"; import connectApp from "@ledgerhq/live-common/hw/connectApp"; +if (getEnv("MOCK")) { + window.mock.events.mockDeviceEvent({ type: "opened" }); +} const action = createAction(getEnv("MOCK") ? mockedEventEmitter : connectApp); const StepConnectDevice = ({ currency, transitionTo, flow }: StepProps) => { invariant(currency, "No crypto asset given"); diff --git a/apps/ledger-live-desktop/tests/specs/accounts/account.spec.ts b/apps/ledger-live-desktop/tests/specs/accounts/account.spec.ts index 72390e52a4a1..6b956399516c 100644 --- a/apps/ledger-live-desktop/tests/specs/accounts/account.spec.ts +++ b/apps/ledger-live-desktop/tests/specs/accounts/account.spec.ts @@ -9,7 +9,7 @@ import { AccountsPage } from "../../models/AccountsPage"; test.use({ userdata: "skip-onboarding" }); -const currencies = ["BTC", "LTC", "ETH", "ATOM", "XTZ", "XRP", "Tron"]; +const currencies = ["BTC", "LTC", "ETH", "ATOM", "XTZ", "XRP", "Tron", "optimism", "kava evm"]; test.describe.parallel("Accounts @smoke", () => { for (const currency of currencies) { diff --git a/apps/ledger-live-mobile/src/screens/AddAccounts/01-SelectCrypto.tsx b/apps/ledger-live-mobile/src/screens/AddAccounts/01-SelectCrypto.tsx index 8298255148a5..fe0d09d5433e 100644 --- a/apps/ledger-live-mobile/src/screens/AddAccounts/01-SelectCrypto.tsx +++ b/apps/ledger-live-mobile/src/screens/AddAccounts/01-SelectCrypto.tsx @@ -85,7 +85,7 @@ export default function AddAccountsSelectCrypto({ navigation, route }: Props) { const base = useFeature("currencyBase"); const baseGoerli = useFeature("currencyBaseGoerli"); const klaytn = useFeature("currencyKlaytn"); - + const mock = useEnv("MOCK"); const featureFlaggedCurrencies = useMemo( () => ({ // Keys in this list must match an existing currency.id @@ -165,9 +165,11 @@ export default function AddAccountsSelectCrypto({ navigation, route }: Props) { const currencies = [...listSupportedCurrencies(), ...listSupportedTokens()].filter( ({ id }) => filterCurrencyIds.length <= 0 || filterCurrencyIds.includes(id), ); - const deactivatedCurrencies = Object.entries(featureFlaggedCurrencies) - .filter(([, feature]) => !feature?.enabled) - .map(([name]) => name); + const deactivatedCurrencies = mock + ? [] + : Object.entries(featureFlaggedCurrencies) + .filter(([, feature]) => !feature?.enabled) + .map(([name]) => name); const currenciesFiltered = currencies.filter(c => !deactivatedCurrencies.includes(c.id)); @@ -175,7 +177,7 @@ export default function AddAccountsSelectCrypto({ navigation, route }: Props) { return currenciesFiltered.filter(c => c.type !== "CryptoCurrency" || !c.isTestnetFor); } return currenciesFiltered; - }, [devMode, featureFlaggedCurrencies, filterCurrencyIds]); + }, [devMode, featureFlaggedCurrencies, filterCurrencyIds, mock]); const sortedCryptoCurrencies = useCurrenciesByMarketcap(cryptoCurrencies); diff --git a/libs/coin-framework/src/mocks/account.ts b/libs/coin-framework/src/mocks/account.ts index b43773607331..fca0a4ecb878 100644 --- a/libs/coin-framework/src/mocks/account.ts +++ b/libs/coin-framework/src/mocks/account.ts @@ -66,6 +66,8 @@ const hardcodedMarketcap = [ "qtum", "ethereum/erc20/huobitoken", "bitcoin_gold", + "kava_evm", + "optimism", "ethereum/erc20/paxos_standard__pax_", "ethereum/erc20/trueusd", "ethereum/erc20/omg", @@ -177,6 +179,10 @@ const currencyIdApproxMarketPrice: Record = { dash: 0.0003367, peercoin: 0.000226, zcash: 0.000205798, + polygon: 1.0e-15, + bsc: 5.0e-14, + optimism: 2.0e-15, + kava_evm: 2.0e-16, }; // mock only use subset of cryptocurrencies to not affect tests when adding coins const currencies = listCryptoCurrencies().filter(c => currencyIdApproxMarketPrice[c.id]); diff --git a/libs/coin-framework/src/mocks/helpers.ts b/libs/coin-framework/src/mocks/helpers.ts index 8583ad27d11e..15c97762c028 100644 --- a/libs/coin-framework/src/mocks/helpers.ts +++ b/libs/coin-framework/src/mocks/helpers.ts @@ -22,7 +22,7 @@ export function genHex(length: number, rng: Prando): string { export function genAddress(currency: CryptoCurrency | TokenCurrency, rng: Prando): string { if ( currency.type === "CryptoCurrency" - ? currency.family === "ethereum" // all eth family + ? currency.family === "ethereum" || currency.family === "evm" // all evm family : currency.id.startsWith("ethereum") // erc20 case ) { return `0x${genHex(40, rng)}`; diff --git a/libs/ledger-live-common/src/families/evm/bridge/mock.ts b/libs/ledger-live-common/src/families/evm/bridge/mock.ts new file mode 100644 index 000000000000..dfd5ee135ca8 --- /dev/null +++ b/libs/ledger-live-common/src/families/evm/bridge/mock.ts @@ -0,0 +1,114 @@ +import { BigNumber } from "bignumber.js"; +import { NotEnoughBalance, RecipientRequired } from "@ledgerhq/errors"; +import type { Transaction } from "@ledgerhq/coin-evm/types/index"; +import type { AccountBridge, CurrencyBridge } from "@ledgerhq/types-live"; +import { getMainAccount } from "../../../account"; +import { + scanAccounts, + signOperation, + broadcast, + sync, + makeAccountBridgeReceive, +} from "../../../bridge/mockHelpers"; +import { defaultUpdateTransaction } from "@ledgerhq/coin-framework/bridge/jsHelpers"; +import { getGasLimit } from "@ledgerhq/coin-evm/logic"; +import { getTypedTransaction } from "@ledgerhq/coin-evm/transaction"; +const receive = makeAccountBridgeReceive(); + +const defaultGetFees = (_a, t: any) => (t.gasPrice || new BigNumber(0)).times(getGasLimit(t)); + +const createTransaction = (): Transaction => ({ + family: "evm", + mode: "send", + amount: new BigNumber(10000000000), + nonce: 0, + recipient: "", + gasPrice: new BigNumber(10000000000), + gasLimit: new BigNumber(21000), + chainId: 2222, + useAllAmount: false, + subAccountId: null, +}); + +const estimateMaxSpendable = ({ account, parentAccount, transaction }) => { + const mainAccount = getMainAccount(account, parentAccount); + const estimatedFees = parentAccount + ? new BigNumber(0) + : transaction + ? defaultGetFees(mainAccount, transaction) + : new BigNumber(1000000000000); + return Promise.resolve(BigNumber.max(0, account.balance.minus(estimatedFees))); +}; + +const getTransactionStatus = (a, t) => { + const errors: { + amount?: Error; + recipient?: Error; + } = {}; + const warnings: { + feeTooHigh?: Error; + gasLimit?: Error; + } = {}; + const tokenAccount = !t.subAccountId + ? null + : a.subAccounts && a.subAccounts.find(ta => ta.id === t.subAccountId); + const account = tokenAccount || a; + const useAllAmount = !!t.useAllAmount; + const estimatedFees = defaultGetFees(a, t); + const totalSpent = useAllAmount + ? account.balance + : tokenAccount + ? new BigNumber(t.amount) + : new BigNumber(t.amount).plus(estimatedFees); + const amount = useAllAmount + ? tokenAccount + ? new BigNumber(t.amount) + : account.balance.minus(estimatedFees) + : new BigNumber(t.amount); + + // Fill up transaction errors... + if (totalSpent.gt(account.balance)) { + errors.amount = new NotEnoughBalance(); + } + if (!t.recipient) { + errors.recipient = new RecipientRequired(""); + } + return Promise.resolve({ + errors, + warnings, + estimatedFees, + amount, + totalSpent, + }); +}; + +const prepareTransaction = async (_a, t) => { + const typedTransaction = getTypedTransaction(t, { + gasPrice: new BigNumber(50), + maxFeePerGas: new BigNumber(50), + maxPriorityFeePerGas: new BigNumber(50), + nextBaseFee: new BigNumber(50), + }); + return typedTransaction; +}; + +const accountBridge: AccountBridge = { + createTransaction, + updateTransaction: defaultUpdateTransaction, + getTransactionStatus, + estimateMaxSpendable, + prepareTransaction, + sync, + receive, + signOperation, + broadcast, +}; +const currencyBridge: CurrencyBridge = { + preload: () => Promise.resolve({}), + hydrate: () => {}, + scanAccounts, +}; +export default { + currencyBridge, + accountBridge, +}; diff --git a/libs/ledger-live-common/src/generated/bridge/mock.ts b/libs/ledger-live-common/src/generated/bridge/mock.ts index 47add9d36e0e..353ea5e03cad 100644 --- a/libs/ledger-live-common/src/generated/bridge/mock.ts +++ b/libs/ledger-live-common/src/generated/bridge/mock.ts @@ -2,6 +2,7 @@ import algorand from "../../families/algorand/bridge/mock"; import bitcoin from "../../families/bitcoin/bridge/mock"; import cosmos from "../../families/cosmos/bridge/mock"; import ethereum from "../../families/ethereum/bridge/mock"; +import evm from "../../families/evm/bridge/mock"; import polkadot from "../../families/polkadot/bridge/mock"; import ripple from "../../families/ripple/bridge/mock"; import solana from "../../families/solana/bridge/mock"; @@ -14,6 +15,7 @@ export default { bitcoin, cosmos, ethereum, + evm, polkadot, ripple, solana,