Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat/live 15126 aptos unit tests bride boradcast prepare transactions #8742

Open
wants to merge 4 commits into
base: feat/integrate-aptos
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 37 additions & 0 deletions libs/ledger-live-common/src/families/aptos/bridge/js.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import bridge from "./js";

describe("Aptos bridge interface ", () => {
describe("currencyBridge ", () => {
it("should contain all methods", () => {
expect(bridge.currencyBridge.preload).toBeDefined();
expect(typeof bridge.currencyBridge.preload).toBe("function");
expect(bridge.currencyBridge.hydrate).toBeDefined();
expect(typeof bridge.currencyBridge.hydrate).toBe("function");
expect(bridge.currencyBridge.scanAccounts).toBeDefined();
expect(typeof bridge.currencyBridge.scanAccounts).toBe("function");
});
});

describe("accountBridge ", () => {
it("should contain all methods", () => {
expect(bridge.accountBridge.estimateMaxSpendable).toBeDefined();
expect(typeof bridge.accountBridge.estimateMaxSpendable).toBe("function");
expect(bridge.accountBridge.createTransaction).toBeDefined();
expect(typeof bridge.accountBridge.createTransaction).toBe("function");
expect(bridge.accountBridge.updateTransaction).toBeDefined();
expect(typeof bridge.accountBridge.updateTransaction).toBe("function");
expect(bridge.accountBridge.getTransactionStatus).toBeDefined();
expect(typeof bridge.accountBridge.getTransactionStatus).toBe("function");
expect(bridge.accountBridge.prepareTransaction).toBeDefined();
expect(typeof bridge.accountBridge.prepareTransaction).toBe("function");
expect(bridge.accountBridge.sync).toBeDefined();
expect(typeof bridge.accountBridge.sync).toBe("function");
expect(bridge.accountBridge.receive).toBeDefined();
expect(typeof bridge.accountBridge.receive).toBe("function");
expect(bridge.accountBridge.signOperation).toBeDefined();
expect(typeof bridge.accountBridge.signOperation).toBe("function");
expect(bridge.accountBridge.broadcast).toBeDefined();
expect(typeof bridge.accountBridge.broadcast).toBe("function");
});
});
});
4 changes: 2 additions & 2 deletions libs/ledger-live-common/src/families/aptos/bridge/js.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,6 @@ import estimateMaxSpendable from "../js-estimateMaxSpendable";
import signOperation from "../js-signOperation";
import broadcast from "../js-broadcast";

const receive = makeAccountBridgeReceive();

const currencyBridge: CurrencyBridge = {
preload: () => Promise.resolve({}),
hydrate: () => {},
Expand All @@ -24,6 +22,8 @@ const updateTransaction = (t: Transaction, patch: Partial<Transaction>): Transac
...patch,
});

const receive = makeAccountBridgeReceive();

const accountBridge: AccountBridge<Transaction> = {
estimateMaxSpendable,
createTransaction,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,12 @@ import type { Account } from "@ledgerhq/types-live";
import type { TransactionStatus } from "../..//generated/types";
import type { Transaction } from "./types";

import { isValidAddress } from "./logic";
import {
SequenseNumberTooNewError,
SequenseNumberTooOldError,
TransactionExpiredError,
} from "./errors";
import { AccountAddress } from "@aptos-labs/ts-sdk";

const getTransactionStatus = async (a: Account, t: Transaction): Promise<TransactionStatus> => {
const errors: Record<string, any> = {};
Expand All @@ -44,7 +44,7 @@ const getTransactionStatus = async (a: Account, t: Transaction): Promise<Transac

if (!t.recipient) {
errors.recipient = new RecipientRequired();
} else if (!isValidAddress(t.recipient)) {
} else if (!AccountAddress.isValid({ input: t.recipient })) {
errors.recipient = new InvalidAddress("", { currencyName: a.currency.name });
} else if (t.recipient === a.freshAddress) {
errors.recepient = new InvalidAddressBecauseDestinationIsAlsoSource();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
import prepareTransaction from "./js-prepareTransaction";
import { AptosAPI } from "./api";
import { getEstimatedGas } from "./js-getFeesForTransaction";
import { getMaxSendBalance } from "./logic";
import BigNumber from "bignumber.js";
import type { Account } from "@ledgerhq/types-live";
import type { Transaction } from "./types";

jest.mock("./api");
jest.mock("./js-getFeesForTransaction");
jest.mock("./logic");

describe("Aptos prepareTransaction", () => {
describe("prepareTransaction", () => {
let account: Account;
let transaction: Transaction;

beforeEach(() => {
account = {
id: "test-account-id",
name: "Test Account",
currency: {
id: "aptos",
name: "Aptos",
ticker: "APT",
units: [{ name: "Aptos", code: "APT", magnitude: 6 }],
},
spendableBalance: new BigNumber(1000),
balance: new BigNumber(1000),
blockHeight: 0,
operations: [],
pendingOperations: [],
unit: { code: "APT", name: "Aptos", magnitude: 6 },
lastSyncDate: new Date(),
subAccounts: [],
} as unknown as Account;

transaction = {
amount: new BigNumber(0),
recipient: "",
useAllAmount: false,
fees: new BigNumber(0),
firstEmulation: true,
options: {},
} as Transaction;
});

it("should return the transaction if recipient is not set", async () => {
const result = await prepareTransaction(account, transaction);
expect(result).toEqual(transaction);
});

it("should return the transaction with zero fees if amount is zero and useAllAmount is false", async () => {
transaction.recipient = "test-recipient";
const result = await prepareTransaction(account, transaction);
expect(result.fees?.isZero()).toBe(true);
});

it("should set the amount to max sendable balance if useAllAmount is true", async () => {
transaction.recipient = "test-recipient";
transaction.useAllAmount = true;
(getMaxSendBalance as jest.Mock).mockReturnValue(new BigNumber(900));
(getEstimatedGas as jest.Mock).mockResolvedValue({
fees: new BigNumber(2000),
estimate: { maxGasAmount: new BigNumber(200), gasUnitPrice: new BigNumber(10) },
errors: {},
});

const result = await prepareTransaction(account, transaction);
expect(result.amount.isEqualTo(new BigNumber(900))).toBe(true);
expect(result.fees?.isEqualTo(new BigNumber(2000))).toBe(true);
expect(new BigNumber(result.estimate.maxGasAmount).isEqualTo(new BigNumber(200))).toBe(true);
expect(result.errors).toEqual({});
});

it("should call getEstimatedGas and set the transaction fees, estimate, and errors", async () => {
transaction.recipient = "test-recipient";
transaction.amount = new BigNumber(100);
(getEstimatedGas as jest.Mock).mockResolvedValue({
fees: new BigNumber(10),
estimate: { maxGasAmount: new BigNumber(200) },
errors: {},
});

const result = await prepareTransaction(account, transaction);
expect(getEstimatedGas).toHaveBeenCalledWith(account, transaction, expect.any(AptosAPI));
expect(result.fees?.isEqualTo(new BigNumber(10))).toBe(true);
expect(new BigNumber(result.estimate.maxGasAmount).isEqualTo(new BigNumber(200))).toBe(true);
expect(result.errors).toEqual({});
});

it("should set firstEmulation to false after the first call", async () => {
transaction.recipient = "test-recipient";
transaction.amount = new BigNumber(100);
(getEstimatedGas as jest.Mock).mockResolvedValue({
fees: new BigNumber(10),
estimate: { maxGasAmount: new BigNumber(200) },
errors: {},
});

const result = await prepareTransaction(account, transaction);
expect(result.firstEmulation).toBe(false);
});

//--------------------------------------------------------------------------------
it("should return the transaction with updated fees and estimate if recipient is set and amount is not zero", async () => {
transaction.recipient = "test-recipient";
transaction.amount = new BigNumber(100);
(getEstimatedGas as jest.Mock).mockResolvedValue({
fees: new BigNumber(2000),
estimate: { maxGasAmount: new BigNumber(200), gasUnitPrice: new BigNumber(10) },
errors: {},
});

const result = await prepareTransaction(account, transaction);
expect(result.fees?.isEqualTo(new BigNumber(2000))).toBe(true);
expect(new BigNumber(result.estimate.maxGasAmount).isEqualTo(new BigNumber(200))).toBe(true);
expect(result.errors).toEqual({});
});

it("should set maxGasAmount in options if firstEmulation is true", async () => {
transaction.recipient = "test-recipient";
transaction.amount = new BigNumber(100);
transaction.firstEmulation = true;
(getEstimatedGas as jest.Mock).mockResolvedValue({
fees: new BigNumber(2000),
estimate: { maxGasAmount: new BigNumber(200), gasUnitPrice: new BigNumber(10) },
errors: {},
});

const result = await prepareTransaction(account, transaction);
expect(new BigNumber(result.estimate.maxGasAmount).isEqualTo(new BigNumber(200))).toBe(true);
expect(result.firstEmulation).toBe(false);
});

it("should not change transaction.options.maxGasAmount if firstEmulation is false", async () => {
transaction.recipient = "test-recipient";
transaction.amount = new BigNumber(100);
transaction.firstEmulation = false;
(getEstimatedGas as jest.Mock).mockResolvedValue({
fees: new BigNumber(2000),
estimate: { maxGasAmount: new BigNumber(200), gasUnitPrice: new BigNumber(10) },
errors: {},
});

const result = await prepareTransaction(account, transaction);
expect(result.options.maxGasAmount).toBeUndefined();
expect(result.firstEmulation).toBe(false);
});
});
});
127 changes: 126 additions & 1 deletion libs/ledger-live-common/src/families/aptos/logic.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,133 @@ import {
getAptosAmounts,
getFunctionAddress,
isChangeOfAptos,
isTestnet,
processRecipients,
getMaxSendBalance,
normalizeTransactionOptions,
getBlankOperation,
} from "./logic";
import type { AptosTransaction } from "./types";
import type { AptosTransaction, Transaction } from "./types";

jest.mock("@ledgerhq/cryptoassets", () => ({
getCryptoCurrencyById: jest.fn(),
}));

describe("Aptos logic ", () => {
describe("isTestnet", () => {
it("should return true for testnet currencies", () => {
expect(isTestnet("aptos_testnet")).toBe(true);
});

it("should return false for mainnet currencies", () => {
expect(isTestnet("aptos")).toBe(false);
});
});

describe("getMaxSendBalance", () => {
it("should return the correct max send balance when amount is greater than total gas", () => {
const amount = new BigNumber(1000000);
const gas = new BigNumber(200);
const gasPrice = new BigNumber(100);
const result = getMaxSendBalance(amount, gas, gasPrice);
expect(result.isEqualTo(amount.minus(gas.multipliedBy(gasPrice)))).toBe(true);
});

it("should return zero when amount is less than total gas", () => {
const amount = new BigNumber(1000);
const gas = new BigNumber(200);
const gasPrice = new BigNumber(100);
const result = getMaxSendBalance(amount, gas, gasPrice);
expect(result.isEqualTo(new BigNumber(0))).toBe(true);
});

it("should return zero when amount is equal to total gas", () => {
const amount = new BigNumber(20000);
const gas = new BigNumber(200);
const gasPrice = new BigNumber(100);
const result = getMaxSendBalance(amount, gas, gasPrice);
expect(result.isEqualTo(new BigNumber(0))).toBe(true);
});

it("should handle zero amount", () => {
const amount = new BigNumber(0);
const gas = new BigNumber(200);
const gasPrice = new BigNumber(100);
const result = getMaxSendBalance(amount, gas, gasPrice);
expect(result.isEqualTo(new BigNumber(0))).toBe(true);
});

it("should handle zero gas and gas price", () => {
const amount = new BigNumber(1000000);
const gas = new BigNumber(0);
const gasPrice = new BigNumber(0);
const result = getMaxSendBalance(amount, gas, gasPrice);
expect(result.isEqualTo(amount)).toBe(true);
});
});

describe("normalizeTransactionOptions", () => {
it("should normalize transaction options", () => {
const options: Transaction["options"] = {
maxGasAmount: "1000",
gasUnitPrice: "10",
sequenceNumber: "1",
expirationTimestampSecs: "1000000",
};

const result = normalizeTransactionOptions(options);
expect(result).toEqual(options);
});

it("should return undefined for empty values", () => {
const options: Transaction["options"] = {
maxGasAmount: "",
gasUnitPrice: "",
sequenceNumber: undefined,
expirationTimestampSecs: "1000000",
};

const result = normalizeTransactionOptions(options);
expect(result).toEqual({
maxGasAmount: undefined,
gasUnitPrice: undefined,
sequenceNumber: undefined,
expirationTimestampSecs: "1000000",
});
});
});

describe("getBlankOperation", () => {
it("should return a blank operation", () => {
const tx: AptosTransaction = {
hash: "0x123",
block: { hash: "0xabc", height: 1 },
timestamp: "1000000",
sequence_number: "1",
} as unknown as AptosTransaction;

const id = "test-id";
const result = getBlankOperation(tx, id);

expect(result).toEqual({
id: "",
hash: "0x123",
type: "",
value: new BigNumber(0),
fee: new BigNumber(0),
blockHash: "0xabc",
blockHeight: 1,
senders: [],
recipients: [],
accountId: id,
date: new Date(1000),
extra: { version: undefined },
transactionSequenceNumber: 1,
hasFailed: false,
});
});
});
});

describe("Aptos sync logic ", () => {
describe("compareAddress", () => {
Expand Down Expand Up @@ -446,4 +570,5 @@ describe("Aptos sync logic ", () => {
expect(result).toEqual(new BigNumber(90).negated()); // 100 - 10
});
});

});
Loading
Loading