Skip to content

Commit

Permalink
00616 HashScan fails to display token balance when token has very lar…
Browse files Browse the repository at this point in the history
…ge number of decimals (#645)

Signed-off-by: Simon Viénot <simon.vienot@icloud.com>
  • Loading branch information
svienot committed Jun 15, 2023
1 parent 0c35c95 commit 3067e04
Show file tree
Hide file tree
Showing 3 changed files with 93 additions and 19 deletions.
50 changes: 32 additions & 18 deletions src/components/values/TokenAmount.vue
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,13 @@
<!-- --------------------------------------------------------------------------------------------------------------- -->

<template>
<span class="is-numeric" :class="{'mr-2': showExtra && tokenId}">{{ formattedAmount }}</span>
<span v-if="decimalOverflow" :class="{'mr-2': showExtra && tokenId}">?</span>
<span v-else class="is-numeric" :class="{'mr-2': showExtra && tokenId}">{{ formattedAmount }}</span>
<span v-if="showExtra && tokenId != null">
<TokenExtra v-bind:token-id="tokenId" v-bind:use-anchor="useAnchor"/>
</span>
<InfoTooltip v-if="decimalOverflow"
:label="`This token amount cannot be displayed because the number of decimals (${decimalCount}) of the token is too large`"/>
</template>

<!-- --------------------------------------------------------------------------------------------------------------- -->
Expand All @@ -40,13 +43,15 @@ import {TokenInfo} from "@/schemas/HederaSchemas";
import {TokenInfoCache} from "@/utils/cache/TokenInfoCache";
import TokenExtra from "@/components/values/TokenExtra.vue";
import {initialLoadingKey} from "@/AppKeys";
import InfoTooltip from "@/components/InfoTooltip.vue";
export const MAX_TOKEN_SUPPLY = 9223372036854775807n
export const MAX_DECIMALS = 20
export default defineComponent({
name: "TokenAmount",
components: {TokenExtra},
components: {InfoTooltip, TokenExtra},
props: {
amount: BigInt,
tokenId: String,
Expand All @@ -68,9 +73,9 @@ export default defineComponent({
if (response.value !== null) {
if (props.amount) {
if (props.amount > MAX_TOKEN_SUPPLY) {
result = formatTokenAmount(MAX_TOKEN_SUPPLY, response.value.decimals)
result = formatTokenAmount(MAX_TOKEN_SUPPLY, decimalCount.value)
} else {
result = formatTokenAmount(props.amount, response.value.decimals)
result = formatTokenAmount(props.amount, decimalCount.value)
}
} else if (initialLoading.value) {
result = ""
Expand All @@ -93,6 +98,21 @@ export default defineComponent({
return result
})
const decimalOverflow = computed(() => {
return decimalCount.value ? decimalCount.value > MAX_DECIMALS : false
})
const decimalCount = computed(() => {
let result: number
if (response.value?.decimals) {
const n = Number(response.value.decimals)
result = isNaN(n) ? 0 : Math.floor(n)
} else {
result = 0
}
return result
})
const updateResponse = () => {
if (props.tokenId) {
TokenInfoCache.instance.lookup(props.tokenId).then((r: TokenInfo | null) => {
Expand All @@ -113,14 +133,19 @@ export default defineComponent({
updateResponse()
})
return { formattedAmount, extra, initialLoading }
return {
formattedAmount,
extra,
decimalOverflow,
decimalCount,
initialLoading
}
}
});
function formatTokenAmount(rawAmount: bigint, decimals: string|undefined): string {
function formatTokenAmount(rawAmount: bigint, decimalCount: number): string {
let result: string
const decimalCount = computeDecimalCount(decimals) ?? 0
const amountFormatter = new Intl.NumberFormat('en-US', {
minimumFractionDigits: decimalCount,
maximumFractionDigits: decimalCount
Expand All @@ -135,17 +160,6 @@ function formatTokenAmount(rawAmount: bigint, decimals: string|undefined): strin
return result
}
function computeDecimalCount(decimals: string|undefined): number|null {
let result: number|null
if (decimals) {
const n = Number(decimals)
result = isNaN(n) ? null : Math.floor(n)
} else {
result = null
}
return result
}
function makeExtra(response: TokenInfo): string {
const name = response.name
const symbol = response.symbol
Expand Down
30 changes: 30 additions & 0 deletions tests/unit/Mocks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -433,6 +433,36 @@ export const SAMPLE_TOKENS = {
]
}

export const SAMPLE_TOKEN_WITH_LARGE_DECIMAL_COUNT = {
"admin_key": {"_type": "ED25519", "key": "d6e8334cd8594e88c82ff266b4974b4e4ac596962dcfab7314f935e7fdda672f"},
"auto_renew_account": "0.0.13688300",
"auto_renew_period": 7776000,
"created_timestamp": "1685137814.521997638",
"custom_fees": {"created_timestamp": "1685137814.521997638", "fixed_fees": [], "fractional_fees": []},
"decimals": "75",
"deleted": false,
"expiry_timestamp": 1692913814521997638,
"fee_schedule_key": null,
"freeze_default": false,
"freeze_key": null,
"initial_supply": "100000000000000",
"kyc_key": null,
"max_supply": "10000000000000000",
"memo": "",
"modified_timestamp": "1685137814.521997638",
"name": "TestToken0",
"pause_key": null,
"pause_status": "NOT_APPLICABLE",
"supply_key": {"_type": "ED25519", "key": "d6e8334cd8594e88c82ff266b4974b4e4ac596962dcfab7314f935e7fdda672f"},
"supply_type": "FINITE",
"symbol": "TTOK0",
"token_id": "0.0.13688500",
"total_supply": "100000000000000",
"treasury_account_id": "0.0.13688300",
"type": "FUNGIBLE_COMMON",
"wipe_key": null
}

export const SAMPLE_BALANCES = {
"timestamp": "1646726400.100874000",
"balances": [
Expand Down
32 changes: 31 additions & 1 deletion tests/unit/values/TokenAmount.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,16 +27,19 @@

import {flushPromises, mount} from "@vue/test-utils";
import router from "@/router";
import {SAMPLE_TOKEN, SAMPLE_TOKEN_DUDE} from "../Mocks";
import {SAMPLE_TOKEN, SAMPLE_TOKEN_DUDE, SAMPLE_TOKEN_WITH_LARGE_DECIMAL_COUNT} from "../Mocks";
import TokenAmount from "@/components/values/TokenAmount.vue";
import MockAdapter from "axios-mock-adapter";
import axios from "axios";
import Oruga from "@oruga-ui/oruga-next";

const mock = new MockAdapter(axios);
const matcher = "/api/v1/tokens/" + SAMPLE_TOKEN.token_id
mock.onGet(matcher).reply(200, SAMPLE_TOKEN);
const matcher2 = "/api/v1/tokens/" + SAMPLE_TOKEN_DUDE.token_id
mock.onGet(matcher2).reply(200, SAMPLE_TOKEN_DUDE);
const matcher3 = "/api/v1/tokens/" + SAMPLE_TOKEN_WITH_LARGE_DECIMAL_COUNT.token_id
mock.onGet(matcher3).reply(200, SAMPLE_TOKEN_WITH_LARGE_DECIMAL_COUNT);

describe("TokenAmount.vue", () => {

Expand Down Expand Up @@ -140,4 +143,31 @@ describe("TokenAmount.vue", () => {
await flushPromises()
});

it("should detect too large decimal count", async () => {

await router.push("/") // To avoid "missing required param 'network'" error

let testAmount = 42

const wrapper = mount(TokenAmount, {
global: {
plugins: [router, Oruga]
},
props: {
amount: BigInt(testAmount),
tokenId: SAMPLE_TOKEN_WITH_LARGE_DECIMAL_COUNT.token_id,
showExtra: true
},
});
await flushPromises()

expect(wrapper.get('span').text()).toBe("?")
expect(wrapper.get('a').attributes('href')).toMatch(RegExp("/token/" + SAMPLE_TOKEN_WITH_LARGE_DECIMAL_COUNT.token_id + "$"))
expect(wrapper.get('.h-is-extra-text').text()).toBe(SAMPLE_TOKEN_WITH_LARGE_DECIMAL_COUNT.symbol)
expect(wrapper.text()).toBe("?TTOK0This token amount cannot be displayed because the number of decimals (75) of the token is too large")

wrapper.unmount()
await flushPromises()
});

});

0 comments on commit 3067e04

Please sign in to comment.