Skip to content

Commit

Permalink
Merge 65fb875 into fcbfca2
Browse files Browse the repository at this point in the history
  • Loading branch information
may01 authored Dec 19, 2024
2 parents fcbfca2 + 65fb875 commit f7ebace
Show file tree
Hide file tree
Showing 6 changed files with 322 additions and 35 deletions.
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

0 comments on commit f7ebace

Please sign in to comment.