Skip to content

Commit

Permalink
Fix: Rework format amount (#824)
Browse files Browse the repository at this point in the history
* Output should be rounded by math rules instead of "round to floor".

Signed-off-by: Eugene Panteleymonchuk <[email protected]>

* Revert changes. Change logic for format amount. Add tests.

Signed-off-by: Eugene Panteleymonchuk <[email protected]>

* Add tests.

Signed-off-by: Eugene Panteleymonchuk <[email protected]>

* All tests passed (included tests before);

Signed-off-by: Eugene Panteleymonchuk <[email protected]>

* Improve toFixedNoRound function.

Signed-off-by: Eugene Panteleymonchuk <[email protected]>

* Revert unchanged files.

Signed-off-by: Eugene Panteleymonchuk <[email protected]>

* feat: Improve valueFormatHelper test naming. Refactor naming. Fix transactionHelper test unable to run with @iota/sdk-wasm

---------

Signed-off-by: Eugene Panteleymonchuk <[email protected]>
Co-authored-by: Mario Sarcevic <[email protected]>
  • Loading branch information
panteleymonchuk and msarcev authored Nov 16, 2023
1 parent b8314f3 commit 2b481f6
Show file tree
Hide file tree
Showing 5 changed files with 121 additions and 39 deletions.
Original file line number Diff line number Diff line change
@@ -1,23 +1,23 @@
import { TransactionsHelper } from "./transactionsHelper";
import { Converter } from "./convertUtils";

describe("Transaction helper", () => {
describe("convertToBigEndian", () => {
it("should convert little endian encoded hex to big endian", () => {
const outputIndex = "0200";
expect(TransactionsHelper.convertToBigEndian(outputIndex)).toBe("0002");
expect(Converter.convertToBigEndian(outputIndex)).toBe("0002");
});

it("should convert to big endian when the index length is 6", () => {
const outputIndex = "9F8601";
expect(TransactionsHelper.convertToBigEndian(outputIndex)).toBe("01869F");
expect(Converter.convertToBigEndian(outputIndex)).toBe("01869F");
});

it("should convert to big endian when the index length is 8", () => {
const outputIndex = "9F860101";
expect(TransactionsHelper.convertToBigEndian(outputIndex)).toBe("0101869F");
expect(Converter.convertToBigEndian(outputIndex)).toBe("0101869F");
});

it("should convert to big endian if the index has odd length", () => {
const outputIndex = "9F86010";
expect(TransactionsHelper.convertToBigEndian(outputIndex)).toBe("1060F809");
expect(Converter.convertToBigEndian(outputIndex)).toBe("1060F809");
});
});
23 changes: 23 additions & 0 deletions client/src/helpers/stardust/convertUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,29 @@ export class Converter {
return Base64.decode(base64);
}

/**
* Convert little endian to big endian.
* @param index Output index in little endian format.
* @returns Output index in big endian format.
*/
public static convertToBigEndian(index: string) {
const bigEndian = [];
let hexLength = index.length;

if (hexLength % 2 !== 0) {
index = "0".concat(index);
hexLength = index.length;
}

while (hexLength >= 0) {
const slicedBits = index.slice(hexLength - 2, hexLength);
bigEndian.push(slicedBits);
hexLength -= 2;
}

return bigEndian.join("");
}

/**
* Build the static lookup tables.
* @internal
Expand Down
28 changes: 3 additions & 25 deletions client/src/helpers/stardust/transactionsHelper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
TransactionPayload, TreasuryOutput, Unlock, UnlockCondition, UnlockConditionType, UnlockType, Utils, UTXOInput
} from "@iota/sdk-wasm/web";
import bigInt from "big-integer";
import { Converter } from "./convertUtils";
import { HexHelper } from "./hexHelper";
import { IBech32AddressDetails } from "../../models/api/IBech32AddressDetails";
import { IInput } from "../../models/api/stardust/IInput";
Expand Down Expand Up @@ -187,36 +188,13 @@ export class TransactionsHelper {
items.sort((a: any, b: any) => {
const firstIndex: string = a.id ? a.id.slice(-4) : a.outputId.slice(-4);
const secondIndex: string = b.id ? b.id.slice(-4) : b.outputId.slice(-4);
const firstFormattedIndex = this.convertToBigEndian(firstIndex);
const secondFormattedIndex = this.convertToBigEndian(secondIndex);
const firstFormattedIndex = Converter.convertToBigEndian(firstIndex);
const secondFormattedIndex = Converter.convertToBigEndian(secondIndex);

return Number.parseInt(firstFormattedIndex, 16) - Number.parseInt(secondFormattedIndex, 16);
});
}

/**
* Convert little endian to big endian.
* @param index Output index in little endian format.
* @returns Output index in big endian format.
*/
public static convertToBigEndian(index: string) {
const bigEndian = [];
let hexLength = index.length;

if (hexLength % 2 !== 0) {
index = "0".concat(index);
hexLength = index.length;
}

while (hexLength >= 0) {
const slicedBits = index.slice(hexLength - 2, hexLength);
bigEndian.push(slicedBits);
hexLength -= 2;
}

return bigEndian.join("");
}

/**
* Compute BLAKE2b-256 hash for alias.
* @param aliasId Alias id.
Expand Down
65 changes: 65 additions & 0 deletions client/src/helpers/stardust/valueFormatHelper.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { formatAmount } from "./valueFormatHelper";

const tokenInfo = {
"name": "IOTA",
"tickerSymbol": "IOTA",
"unit": "IOTA",
"subunit": "micro",
"decimals": 6,
"useMetricPrefix": false
};

describe("formatAmount", () => {
it("should format 1 subunit properly", () => {
expect(formatAmount(1, tokenInfo)).toBe("0.000001 IOTA");
});

it("should format 10 subunit properly", () => {
expect(formatAmount(10, tokenInfo)).toBe("0.00001 IOTA");
});

it("should format 100 subunit properly", () => {
expect(formatAmount(100, tokenInfo)).toBe("0.0001 IOTA");
});

it("should format 1000 subunit properly", () => {
expect(formatAmount(1000, tokenInfo)).toBe("0.001 IOTA");
});

it("should format 10000 subunit properly", () => {
expect(formatAmount(10000, tokenInfo)).toBe("0.01 IOTA");
});

it("should format 100000 subunit properly", () => {
expect(formatAmount(100000, tokenInfo)).toBe("0.1 IOTA");
});

it("should format 1 unit properly", () => {
expect(formatAmount(1000000, tokenInfo)).toBe("1 IOTA");
});

it("should format 1 unit with fraction properly", () => {
expect(formatAmount(1234567, tokenInfo)).toBe("1.23 IOTA");
});

it("should handle edge case from issue 'explorer/issues/822'", () => {
expect(formatAmount(1140000, tokenInfo)).toBe("1.14 IOTA");
});

it("should honour precision 3", () => {
expect(formatAmount(9999, tokenInfo, false, 3)).toBe("0.009 IOTA");
});

it("should honour precision 4", () => {
expect(formatAmount(9999, tokenInfo, false, 4)).toBe("0.0099 IOTA");
});

it("should honour precision 0", () => {
expect(formatAmount(1450896407249092, tokenInfo, false, 0)).toBe("1450896407 IOTA");
});

it("should format big values properly", () => {
expect(formatAmount(1450896407249092, tokenInfo)).toBe("1450896407.24 IOTA");
});
});

32 changes: 24 additions & 8 deletions client/src/helpers/stardust/valueFormatHelper.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { UnitsHelper } from "@iota/iota.js";
import { INodeInfoBaseToken } from "@iota/sdk-wasm/web";
import React from "react";
import Tooltip from "../../app/components/Tooltip";

/**
* The id of the Genesis block.
*/
Expand All @@ -26,12 +27,11 @@ export function formatAmount(
}

const baseTokenValue = value / Math.pow(10, tokenInfo.decimals);
// useMetricPrefix is broken cause it passes a float value to formatBest
const amount = tokenInfo.useMetricPrefix
? UnitsHelper.formatBest(baseTokenValue)
: `${toFixedNoRound(baseTokenValue, decimalPlaces)} `;
const formattedAmount = toFixedNoRound(baseTokenValue, decimalPlaces);

return `${amount}${tokenInfo.unit}`;
// useMetricPrefix is broken cause it passes a float value to formatBest
const amount = tokenInfo.useMetricPrefix ? UnitsHelper.formatBest(baseTokenValue) : `${formattedAmount} `;
return `${amount}${tokenInfo.unit}`;
}

/**
Expand All @@ -51,9 +51,25 @@ export function formatNumberWithCommas(
* @param precision The decimal places to show.
* @returns The formatted amount.
*/
function toFixedNoRound(value: number, precision: number = 2) {
const factor = Math.pow(10, precision);
return Math.floor(value * factor) / factor;
function toFixedNoRound(value: number, precision: number = 2): string {
const valueString = `${value}`;
const [integer, fraction] = valueString.split(".");
if (!fraction) {
return valueString;
}

if (!precision) {
return integer;
}

const truncatedFraction = fraction.slice(0, precision);

// avoid 0.00 case
if (!Number(truncatedFraction)) {
return `${integer}.${fraction}`;
}

return `${integer}.${truncatedFraction}`;
}

/**
Expand Down

0 comments on commit 2b481f6

Please sign in to comment.