UNPKG

@ledgerhq/live-common

Version:
631 lines • 25.8 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); const bignumber_js_1 = __importDefault(require("bignumber.js")); const currencies_1 = require("../currencies"); const signMessage = __importStar(require("../hw/signMessage/index")); const cryptoCurrencies_1 = require("../mock/fixtures/cryptoCurrencies"); const converters = __importStar(require("./converters")); const logic_1 = require("./logic"); const serializers = __importStar(require("./serializers")); const store_1 = require("@ledgerhq/live-wallet/store"); describe("receiveOnAccountLogic", () => { const walletState = store_1.initialState; // Given const mockPlatformReceiveRequested = jest.fn(); const mockPlatformReceiveFail = jest.fn(); const context = createContextContainingAccountId({ platformReceiveRequested: mockPlatformReceiveRequested, platformReceiveFail: mockPlatformReceiveFail, }, "11", "12"); const uiNavigation = jest.fn(); beforeEach(() => { mockPlatformReceiveRequested.mockClear(); mockPlatformReceiveFail.mockClear(); uiNavigation.mockClear(); }); describe("when nominal case", () => { // Given const accountId = "js:2:ethereum:0x012:"; const expectedResult = "Function called"; beforeEach(() => uiNavigation.mockResolvedValueOnce(expectedResult)); it("calls uiNavigation callback with an accountAddress", async () => { // Given const convertedAccount = { ...createPlatformAccount(), address: "Converted address", }; jest.spyOn(converters, "accountToPlatformAccount").mockReturnValueOnce(convertedAccount); // When const result = await (0, logic_1.receiveOnAccountLogic)(walletState, context, accountId, uiNavigation); // Then expect(uiNavigation).toBeCalledTimes(1); expect(uiNavigation.mock.calls[0][2]).toEqual("Converted address"); expect(result).toEqual(expectedResult); }); it("calls the tracking for success", async () => { // When await (0, logic_1.receiveOnAccountLogic)(walletState, context, accountId, uiNavigation); // Then expect(mockPlatformReceiveRequested).toBeCalledTimes(1); expect(mockPlatformReceiveFail).toBeCalledTimes(0); }); }); describe("when account cannot be found", () => { // Given const nonFoundAccountId = "js:2:ethereum:0x010:"; it("returns an error", async () => { // When await expect(async () => { await (0, logic_1.receiveOnAccountLogic)(walletState, context, nonFoundAccountId, uiNavigation); }).rejects.toThrowError("Account required"); // Then expect(uiNavigation).toBeCalledTimes(0); }); it("calls the tracking for error", async () => { // When await expect(async () => { await (0, logic_1.receiveOnAccountLogic)(walletState, context, nonFoundAccountId, uiNavigation); }).rejects.toThrow(); // Then expect(mockPlatformReceiveRequested).toBeCalledTimes(1); expect(mockPlatformReceiveFail).toBeCalledTimes(1); }); }); }); describe("completeExchangeLogic", () => { // Given const mockPlatformCompleteExchangeRequested = jest.fn(); const context = createContextContainingAccountId({ platformCompleteExchangeRequested: mockPlatformCompleteExchangeRequested, }, "11", "12"); const uiNavigation = jest.fn(); beforeAll(() => { (0, currencies_1.setSupportedCurrencies)(["bitcoin", "ethereum"]); }); afterAll(() => { (0, currencies_1.setSupportedCurrencies)([]); }); beforeEach(() => { mockPlatformCompleteExchangeRequested.mockClear(); uiNavigation.mockClear(); }); describe("when nominal case", () => { // Given const expectedResult = "Function called"; beforeEach(() => uiNavigation.mockResolvedValueOnce(expectedResult)); it("calls uiNavigation callback (token)", async () => { // Given const fromAccount = (0, cryptoCurrencies_1.createFixtureTokenAccount)("16"); const fromParentAccount = (0, cryptoCurrencies_1.createFixtureAccount)("16"); context.accounts = [...context.accounts, fromAccount, fromParentAccount]; const rawPlatformTransaction = createRawEtherumTransaction(); const completeExchangeRequest = { provider: "provider", fromAccountId: "js:2:ethereum:0x16:+ethereum%2Ferc20%2Fusd_tether__erc20_", toAccountId: "js:2:ethereum:0x042:", transaction: rawPlatformTransaction, binaryPayload: "binaryPayload", signature: "signature", feesStrategy: "medium", exchangeType: 8, }; const expectedTransaction = { family: "evm", amount: new bignumber_js_1.default("1000000000"), subAccountId: "js:2:ethereum:0x16:+ethereum%2Ferc20%2Fusd_tether__erc20_", recipient: "0x0123456", nonce: 8, data: Buffer.from("Some data...", "hex"), type: 0, gasPrice: new bignumber_js_1.default("700000"), maxFeePerGas: undefined, maxPriorityFeePerGas: undefined, gasLimit: new bignumber_js_1.default("1200000"), customGasLimit: new bignumber_js_1.default("1200000"), feesStrategy: "medium", mode: "send", useAllAmount: false, chainId: 1, }; // When const result = await (0, logic_1.completeExchangeLogic)(context, completeExchangeRequest, uiNavigation); // Then expect(uiNavigation).toBeCalledTimes(1); expect(uiNavigation.mock.calls[0][0]).toEqual({ provider: "provider", exchange: { fromAccount, fromParentAccount, fromCurrency: fromAccount.token, toAccount: undefined, toParentAccount: undefined, toCurrency: undefined, }, transaction: expectedTransaction, binaryPayload: "binaryPayload", signature: "signature", feesStrategy: "medium", exchangeType: 8, }); expect(result).toEqual(expectedResult); }); it("calls uiNavigation callback (coin)", async () => { // Given const fromAccount = (0, cryptoCurrencies_1.createFixtureAccount)("17"); context.accounts = [...context.accounts, fromAccount]; const rawPlatformTransaction = createRawEtherumTransaction(); const completeExchangeRequest = { provider: "provider", fromAccountId: "js:2:ethereum:0x017:", toAccountId: "js:2:ethereum:0x042:", transaction: rawPlatformTransaction, binaryPayload: "binaryPayload", signature: "signature", feesStrategy: "medium", exchangeType: 8, }; const expectedTransaction = { family: "evm", amount: new bignumber_js_1.default("1000000000"), recipient: "0x0123456", nonce: 8, data: Buffer.from("Some data...", "hex"), gasPrice: new bignumber_js_1.default("700000"), gasLimit: new bignumber_js_1.default("1200000"), customGasLimit: new bignumber_js_1.default("1200000"), feesStrategy: "medium", mode: "send", useAllAmount: false, chainId: 1, subAccountId: undefined, type: 0, maxFeePerGas: undefined, maxPriorityFeePerGas: undefined, }; // When const result = await (0, logic_1.completeExchangeLogic)(context, completeExchangeRequest, uiNavigation); // Then expect(uiNavigation).toBeCalledTimes(1); expect(uiNavigation.mock.calls[0][0]).toEqual({ provider: "provider", exchange: { fromAccount, fromParentAccount: undefined, fromCurrency: fromAccount.currency, toAccount: undefined, toParentAccount: undefined, toCurrency: undefined, }, transaction: expectedTransaction, binaryPayload: "binaryPayload", signature: "signature", feesStrategy: "medium", exchangeType: 8, }); expect(result).toEqual(expectedResult); }); it.each(["slow", "medium", "fast", "custom"])("calls uiNavigation with a transaction that has the %s feeStrategy", async (expectedFeeStrategy) => { // Given const fromAccount = (0, cryptoCurrencies_1.createFixtureAccount)("17"); context.accounts = [...context.accounts, fromAccount]; const rawPlatformTransaction = createRawEtherumTransaction(); const completeExchangeRequest = { provider: "provider", fromAccountId: "js:2:ethereum:0x017:", toAccountId: "js:2:ethereum:0x042:", transaction: rawPlatformTransaction, binaryPayload: "binaryPayload", signature: "signature", feesStrategy: expectedFeeStrategy, exchangeType: 8, }; // When await (0, logic_1.completeExchangeLogic)(context, completeExchangeRequest, uiNavigation); // Then expect(uiNavigation).toBeCalledTimes(1); expect(uiNavigation.mock.calls[0][0]["transaction"].feesStrategy).toEqual(expectedFeeStrategy); }); it("calls the tracking for success", async () => { // Given const completeExchangeRequest = { provider: "provider", fromAccountId: "js:2:ethereum:0x012:", toAccountId: "js:2:ethereum:0x042:", transaction: createRawEtherumTransaction(), binaryPayload: "binaryPayload", signature: "signature", feesStrategy: "feeStrategy", exchangeType: 8, }; // When await (0, logic_1.completeExchangeLogic)(context, completeExchangeRequest, uiNavigation); // Then expect(mockPlatformCompleteExchangeRequested).toBeCalledTimes(1); }); }); describe("when Account is from a different family than the transaction", () => { // Given const expectedResult = "Function called"; beforeEach(() => uiNavigation.mockResolvedValueOnce(expectedResult)); it("returns an error", async () => { // Given const fromAccount = (0, cryptoCurrencies_1.createFixtureAccount)("17"); context.accounts = [...context.accounts, fromAccount]; const rawPlatformTransaction = createRawBitcoinTransaction(); const completeExchangeRequest = { provider: "provider", fromAccountId: "js:2:ethereum:0x017:", toAccountId: "js:2:ethereum:0x042:", transaction: rawPlatformTransaction, binaryPayload: "binaryPayload", signature: "signature", feesStrategy: "feeStrategy", exchangeType: 8, }; // When await expect(async () => { await (0, logic_1.completeExchangeLogic)(context, completeExchangeRequest, uiNavigation); }).rejects.toThrowError("Account and transaction must be from the same family"); // Then expect(uiNavigation).toBeCalledTimes(0); }); }); }); describe("broadcastTransactionLogic", () => { // Given const mockplatformBroadcastFail = jest.fn(); const context = createContextContainingAccountId({ platformBroadcastFail: mockplatformBroadcastFail, }, "11", "12"); const uiNavigation = jest.fn(); beforeEach(() => { mockplatformBroadcastFail.mockClear(); uiNavigation.mockClear(); }); describe("when nominal case", () => { // Given const accountId = "js:2:ethereum:0x012:"; const rawSignedTransaction = createSignedOperationRaw(); it("calls uiNavigation callback with a signedOperation", async () => { // Given const expectedResult = "Function called"; const signedOperation = createSignedOperation(); jest .spyOn(serializers, "deserializePlatformSignedTransaction") .mockReturnValueOnce(signedOperation); uiNavigation.mockResolvedValueOnce(expectedResult); // When const result = await (0, logic_1.broadcastTransactionLogic)(context, accountId, rawSignedTransaction, uiNavigation); // Then expect(uiNavigation).toBeCalledTimes(1); expect(uiNavigation.mock.calls[0][2]).toEqual(signedOperation); expect(result).toEqual(expectedResult); }); it("calls the tracking for success", async () => { // When await (0, logic_1.broadcastTransactionLogic)(context, accountId, rawSignedTransaction, uiNavigation); // Then expect(mockplatformBroadcastFail).toBeCalledTimes(0); }); }); describe("when account cannot be found", () => { // Given const nonFoundAccountId = "js:2:ethereum:0x010:"; const rawSignedTransaction = createSignedOperationRaw(); it("returns an error", async () => { // Given const expectedResult = "Function called"; const signedOperation = createSignedOperation(); jest .spyOn(serializers, "deserializePlatformSignedTransaction") .mockReturnValueOnce(signedOperation); uiNavigation.mockResolvedValueOnce(expectedResult); // When await expect(async () => { await (0, logic_1.broadcastTransactionLogic)(context, nonFoundAccountId, rawSignedTransaction, uiNavigation); }).rejects.toThrowError("Account required"); // Then expect(uiNavigation).toBeCalledTimes(0); }); it("calls the tracking for error", async () => { // When await expect(async () => { await (0, logic_1.broadcastTransactionLogic)(context, nonFoundAccountId, rawSignedTransaction, uiNavigation); }).rejects.toThrow(); // Then expect(mockplatformBroadcastFail).toBeCalledTimes(1); }); }); }); describe("signMessageLogic", () => { // Given const mockPlatformSignMessageRequested = jest.fn(); const mockPlatformSignMessageFail = jest.fn(); const context = createContextContainingAccountId({ platformSignMessageRequested: mockPlatformSignMessageRequested, platformSignMessageFail: mockPlatformSignMessageFail, }, "11", "12"); const uiNavigation = jest.fn(); beforeEach(() => { mockPlatformSignMessageRequested.mockClear(); mockPlatformSignMessageFail.mockClear(); uiNavigation.mockClear(); }); describe("when nominal case", () => { // Given const accountId = "js:2:ethereum:0x012:"; const messageToSign = "Message to sign"; const spyPrepareMessageToSign = jest.spyOn(signMessage, "prepareMessageToSign"); beforeEach(() => spyPrepareMessageToSign.mockClear()); it("calls uiNavigation callback with a signedOperation", async () => { // Given const expectedResult = "Function called"; const formattedMessage = createMessageData(); spyPrepareMessageToSign.mockReturnValueOnce(formattedMessage); uiNavigation.mockResolvedValueOnce(expectedResult); // When const result = await (0, logic_1.signMessageLogic)(context, accountId, messageToSign, uiNavigation); // Then expect(uiNavigation).toBeCalledTimes(1); expect(uiNavigation.mock.calls[0][1]).toEqual(formattedMessage); expect(result).toEqual(expectedResult); }); it("calls the tracking for success", async () => { // When await (0, logic_1.signMessageLogic)(context, accountId, messageToSign, uiNavigation); // Then expect(mockPlatformSignMessageRequested).toBeCalledTimes(1); expect(mockPlatformSignMessageFail).toBeCalledTimes(0); }); }); describe("when account cannot be found", () => { // Given const nonFoundAccountId = "js:2:ethereum:0x010:"; const messageToSign = "Message to sign"; it("returns an error", async () => { // When await expect(async () => { await (0, logic_1.signMessageLogic)(context, nonFoundAccountId, messageToSign, uiNavigation); }).rejects.toThrowError(`account with id "${nonFoundAccountId}" not found`); // Then expect(uiNavigation).toBeCalledTimes(0); }); it("calls the tracking for error", async () => { // When await expect(async () => { await (0, logic_1.signMessageLogic)(context, nonFoundAccountId, messageToSign, uiNavigation); }).rejects.toThrow(); // Then expect(mockPlatformSignMessageRequested).toBeCalledTimes(1); expect(mockPlatformSignMessageFail).toBeCalledTimes(1); }); }); describe("when account found is not of type 'Account'", () => { // Given const tokenAccountId = "15"; const messageToSign = "Message to sign"; context.accounts = [createTokenAccount(tokenAccountId), ...context.accounts]; it("returns an error", async () => { // When await expect(async () => { await (0, logic_1.signMessageLogic)(context, tokenAccountId, messageToSign, uiNavigation); }).rejects.toThrowError("account provided should be the main one"); // Then expect(uiNavigation).toBeCalledTimes(0); }); it("calls the tracking for error", async () => { // When await expect(async () => { await (0, logic_1.signMessageLogic)(context, tokenAccountId, messageToSign, uiNavigation); }).rejects.toThrow(); // Then expect(mockPlatformSignMessageRequested).toBeCalledTimes(1); expect(mockPlatformSignMessageFail).toBeCalledTimes(1); }); }); describe("when inner call prepareMessageToSign raise an error", () => { // Given const accountId = "js:2:ethereum:0x012:"; const messageToSign = "Message to sign"; const spyPrepareMessageToSign = jest.spyOn(signMessage, "prepareMessageToSign"); beforeEach(() => spyPrepareMessageToSign.mockClear()); it("returns an error", async () => { // Given spyPrepareMessageToSign.mockImplementationOnce(() => { throw new Error("Some error"); }); // When await expect(async () => { await (0, logic_1.signMessageLogic)(context, accountId, messageToSign, uiNavigation); }).rejects.toThrowError("Some error"); // Then expect(uiNavigation).toBeCalledTimes(0); }); it("calls the tracking for error", async () => { // Given spyPrepareMessageToSign.mockImplementationOnce(() => { throw new Error("Some error"); }); // When await expect(async () => { await (0, logic_1.signMessageLogic)(context, accountId, messageToSign, uiNavigation); }).rejects.toThrow(); // Then expect(mockPlatformSignMessageRequested).toBeCalledTimes(1); expect(mockPlatformSignMessageFail).toBeCalledTimes(1); }); }); }); function createAppManifest(id = "1") { return { id, private: false, name: "New App Manifest", url: "https://www.ledger.com", homepageUrl: "https://www.ledger.com", supportUrl: "https://www.ledger.com", icon: null, platforms: ["ios", "android", "desktop"], apiVersion: "1.0.0", manifestVersion: "1.0.0", branch: "debug", params: undefined, categories: [], currencies: "*", content: { shortDescription: { en: "short description", }, description: { en: "description", }, }, permissions: [], domains: [], visibility: "complete", }; } function createContextContainingAccountId(tracking, ...accountIds) { return { manifest: createAppManifest(), accounts: [...accountIds.map(val => (0, cryptoCurrencies_1.createFixtureAccount)(val)), (0, cryptoCurrencies_1.createFixtureAccount)()], tracking: tracking, }; } function createSignedOperation() { const operation = { id: "42", hash: "hashed", type: "IN", value: new bignumber_js_1.default(0), fee: new bignumber_js_1.default(0), senders: [], recipients: [], blockHeight: null, blockHash: null, accountId: "14", date: new Date(), extra: {}, }; return { operation, signature: "Signature", }; } function createSignedOperationRaw() { const rawOperation = { id: "12", hash: "123456", type: "CREATE", value: "0", fee: "0", senders: [], recipients: [], blockHeight: null, blockHash: null, accountId: "12", date: "01/01/1970", extra: {}, }; return { operation: rawOperation, signature: "Signature", }; } function createPlatformAccount() { return { id: "12", name: "", address: "", currency: "", balance: new bignumber_js_1.default(0), spendableBalance: new bignumber_js_1.default(0), blockHeight: 0, lastSyncDate: new Date(), }; } function createMessageData() { return { account: (0, cryptoCurrencies_1.createFixtureAccount)("17"), message: "default message", }; } function createTokenAccount(id = "32") { return { type: "TokenAccount", id, parentId: "whatever", token: createTokenCurrency(), balance: new bignumber_js_1.default(0), spendableBalance: new bignumber_js_1.default(0), creationDate: new Date(), operationsCount: 0, operations: [], pendingOperations: [], balanceHistoryCache: { WEEK: { latestDate: null, balances: [] }, HOUR: { latestDate: null, balances: [] }, DAY: { latestDate: null, balances: [] }, }, swapHistory: [], }; } function createTokenCurrency() { return { type: "TokenCurrency", id: "3", contractAddress: "", parentCurrency: (0, cryptoCurrencies_1.createFixtureCryptoCurrency)("eth"), tokenType: "", //-- CurrencyCommon name: "", ticker: "", units: [], }; } function createRawEtherumTransaction() { return { family: "ethereum", amount: "1000000000", recipient: "0x0123456", nonce: 8, data: "Some data...", gasPrice: "700000", gasLimit: "1200000", }; } function createRawBitcoinTransaction() { return { family: "bitcoin", amount: "1000000000", recipient: "0x0123456", feePerByte: "900000", }; } //# sourceMappingURL=logic.test.js.map