From b4b3f5e267adbff5aca3b61b3e5f7aa882eea617 Mon Sep 17 00:00:00 2001 From: Andrew Burnie Date: Thu, 26 May 2022 23:11:42 +0100 Subject: [PATCH] [explorer] handle big numbers for balance aggregation (#2235) * handles balance overflow when working with grouped coin object view * removes submodule * implementing feedback * updating the SDK * all balance, 10 * improves label * adds return type * preserves large balance values * updates explorer yarn.lock * first working version * refactoring * further refactoring * update package json for explorer Co-authored-by: Stella Cannefax --- explorer/client/package.json | 4 +++- explorer/client/src/__tests__/e2e.test.ts | 2 +- .../components/ownedobjects/OwnedObjects.tsx | 21 +++++++++-------- .../client/src/utils/static/mock_data.json | 10 ++++---- .../client/src/utils/static/owned_object.json | 4 ++-- explorer/client/yarn.lock | 23 +++++++++++++++++++ sdk/typescript/package.json | 1 + sdk/typescript/src/rpc/client.ts | 9 +++++++- sdk/typescript/src/types/framework.ts | 4 ++++ sdk/typescript/yarn.lock | 5 ++++ 10 files changed, 63 insertions(+), 20 deletions(-) diff --git a/explorer/client/package.json b/explorer/client/package.json index d8d0b27e06f69..2a063ed050e3f 100644 --- a/explorer/client/package.json +++ b/explorer/client/package.json @@ -6,6 +6,7 @@ "@testing-library/jest-dom": "^5.16.2", "@testing-library/react": "^12.1.2", "@testing-library/user-event": "^13.5.0", + "@types/bn.js": "^5.1.0", "@types/jest": "^27.4.0", "@types/jest-environment-puppeteer": "^5.0.0", "@types/node": "^16.11.24", @@ -35,7 +36,8 @@ "react-ga4": "^1.4.1", "react-router-dom": "^6.2.1", "vanilla-cookieconsent": "^2.8.0", - "web-vitals": "^2.1.4" + "web-vitals": "^2.1.4", + "bn.js": "^5.2.0" }, "scripts": { "start": "react-scripts start", diff --git a/explorer/client/src/__tests__/e2e.test.ts b/explorer/client/src/__tests__/e2e.test.ts index 08856cac9611f..d00a2aa8aff41 100644 --- a/explorer/client/src/__tests__/e2e.test.ts +++ b/explorer/client/src/__tests__/e2e.test.ts @@ -387,7 +387,7 @@ describe('End-to-end Tests', () => { await cssInteract(page) .with(coinGroup(1).field(2)) .get.textContent() - ).toBe('Balance300'); + ).toBe('Balance9007199254740993'); expect( await cssInteract(page) diff --git a/explorer/client/src/components/ownedobjects/OwnedObjects.tsx b/explorer/client/src/components/ownedobjects/OwnedObjects.tsx index 890a8cb9e86da..c40ea852f1a8b 100644 --- a/explorer/client/src/components/ownedobjects/OwnedObjects.tsx +++ b/explorer/client/src/components/ownedobjects/OwnedObjects.tsx @@ -2,6 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 import { Coin, getObjectFields, getObjectId } from '@mysten/sui.js'; +import BN from 'bn.js'; import React, { useCallback, useEffect, @@ -35,7 +36,7 @@ type resultType = { _isCoin: boolean; Version?: string; display?: string; - balance?: number; + balance?: BN; }[]; const DATATYPE_DEFAULT: resultType = [ @@ -71,14 +72,15 @@ function OwnedObjectStatic({ id }: { id: string }) { const objects = findOwnedObjectsfromID(id); if (objects) { - const results = objects?.map(({ objectId }) => { + const results = objects.map(({ objectId }) => { const entry = findDataFromID(objectId, undefined); + const convertToBN = (balance: string): BN => new BN.BN(balance, 10); return { id: entry?.id, Type: entry?.objType, Version: entry?.version, display: entry?.data?.contents?.display, - balance: entry?.data?.contents?.balance, + balance: convertToBN(entry?.data?.contents?.balance), _isCoin: entry?.data?.contents?.balance !== undefined, }; }); @@ -122,10 +124,8 @@ function OwnedObjectAPI({ id }: { id: string }) { const contents = getObjectFields(resp); const url = parseImageURL(contents); const objType = parseObjectType(resp); - // TODO: handle big number by making the balance field - // in resultType a string const balanceValue = - Coin.getBalance(resp)?.toNumber(); + Coin.getBalance(resp); return { id: getObjectId(resp), Type: objType, @@ -232,9 +232,10 @@ function GroupView({ results }: { results: resultType }) { ) ? `${subObjList.reduce( (prev, current) => - // TODO: handle number overflow - prev + current.balance!, - 0 + prev.add( + current.balance! + ), + Coin.getZero() )}` : ''} @@ -422,7 +423,7 @@ function OwnedObjectView({ results }: { results: resultType }) { return (
{key} - {value} + {String(value)}
); } diff --git a/explorer/client/src/utils/static/mock_data.json b/explorer/client/src/utils/static/mock_data.json index dc2e71686bf6d..12451d5dbe8b6 100644 --- a/explorer/client/src/utils/static/mock_data.json +++ b/explorer/client/src/utils/static/mock_data.json @@ -427,12 +427,12 @@ "tx_digest": "Da4vHc9IwbvOYblE8LnrVsqXwryt2Kmms+xnJ7Zx5E4=", "contents": { "id": {}, - "balance": 100 + "balance": "9007199254740991" } } }, { - "id": "standaloneObject200", + "id": "standaloneObject2", "category": "object", "owner": "SingleOwner(k#player2)", "version": "87", @@ -441,7 +441,7 @@ "data": { "contents": { "id": {}, - "balance": 200 + "balance": "2" } } }, @@ -455,7 +455,7 @@ "data": { "contents": { "id": {}, - "balance": 50 + "balance": "50" } } }, @@ -469,7 +469,7 @@ "data": { "contents": { "id": {}, - "balance": 150 + "balance": "150" } } }, diff --git a/explorer/client/src/utils/static/owned_object.json b/explorer/client/src/utils/static/owned_object.json index c3eb69464e6be..92ace9369d636 100644 --- a/explorer/client/src/utils/static/owned_object.json +++ b/explorer/client/src/utils/static/owned_object.json @@ -85,7 +85,7 @@ "objectId": "standaloneObject" }, { - "objectId": "standaloneObject200" + "objectId": "standaloneObject2" }, { "objectId": "standaloneObjectSUI50" @@ -180,7 +180,7 @@ "objectId": "standaloneObject" }, { - "objectId": "standaloneObject200" + "objectId": "standaloneObject2" }, { "objectId": "standaloneObjectSUI50" diff --git a/explorer/client/yarn.lock b/explorer/client/yarn.lock index 2fa5a7e1e0595..0c2db6a175f9d 100644 --- a/explorer/client/yarn.lock +++ b/explorer/client/yarn.lock @@ -1460,6 +1460,7 @@ cross-fetch "^3.1.5" jayson "^3.6.6" js-sha3 "^0.8.0" + lossless-json "^1.0.5" tweetnacl "^1.0.3" util "^0.12.4" @@ -1783,6 +1784,13 @@ dependencies: "@babel/types" "^7.3.0" +"@types/bn.js@^5.1.0": + version "5.1.0" + resolved "https://registry.yarnpkg.com/@types/bn.js/-/bn.js-5.1.0.tgz#32c5d271503a12653c62cf4d2b45e6eab8cebc68" + integrity sha512-QSSVYj7pYFN49kW77o2s9xTCwZ8F2xLbjLLSEVh8D2F4JUhZtPAGOFLTD+ffqksBx/u4cE/KImFjyhqCjn/LIA== + dependencies: + "@types/node" "*" + "@types/body-parser@*": version "1.19.2" resolved "https://registry.yarnpkg.com/@types/body-parser/-/body-parser-1.19.2.tgz#aea2059e28b7658639081347ac4fab3de166e6f0" @@ -6458,6 +6466,11 @@ loose-envify@^1.1.0, loose-envify@^1.4.0: dependencies: js-tokens "^3.0.0 || ^4.0.0" +lossless-json@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/lossless-json/-/lossless-json-1.0.5.tgz#26df1d7d52543a994df07f1b174cf5576fb1482b" + integrity sha512-RicKUuLwZVNZ6ZdJHgIZnSeA05p8qWc5NW0uR96mpPIjN9WDLUg9+kj1esQU1GkPn9iLZVKatSQK5gyiaFHgJA== + lower-case@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/lower-case/-/lower-case-2.0.2.tgz#6fa237c63dbdc4a82ca0fd882e4722dc5e634e28" @@ -7961,6 +7974,11 @@ react-error-overlay@^6.0.11: resolved "https://registry.yarnpkg.com/react-error-overlay/-/react-error-overlay-6.0.11.tgz#92835de5841c5cf08ba00ddd2d677b6d17ff9adb" integrity sha512-/6UZ2qgEyH2aqzYZgQPxEnz33NJ2gNsnHA2o5+o4wW9bLM/JYQitNP9xPhsXwC08hMMovfGe/8retsdDsczPRg== +react-ga4@^1.4.1: + version "1.4.1" + resolved "https://registry.yarnpkg.com/react-ga4/-/react-ga4-1.4.1.tgz#6ee2a2db115ed235b2f2092bc746b4eeeca9e206" + integrity sha512-ioBMEIxd4ePw4YtaloTUgqhQGqz5ebDdC4slEpLgy2sLx1LuZBC9iYCwDymTXzcntw6K1dHX183ulP32nNdG7w== + react-is@^16.13.1: version "16.13.1" resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" @@ -9523,6 +9541,11 @@ validate-npm-package-license@^3.0.1: spdx-correct "^3.0.0" spdx-expression-parse "^3.0.0" +vanilla-cookieconsent@^2.8.0: + version "2.8.0" + resolved "https://registry.yarnpkg.com/vanilla-cookieconsent/-/vanilla-cookieconsent-2.8.0.tgz#651bc9de2517571afb4ef72075d0200ec30ce343" + integrity sha512-ouffZlaEkrGBvYJtYt1p4tB4IQ9gdw1dZR+yJmlTjpA14PUKCWpx9gblWeujpwD5OjXI0/aWHb0M0wdYd2uJ2g== + vary@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" diff --git a/sdk/typescript/package.json b/sdk/typescript/package.json index 9e07f098b9c1c..f326db1bc7794 100644 --- a/sdk/typescript/package.json +++ b/sdk/typescript/package.json @@ -68,6 +68,7 @@ "cross-fetch": "^3.1.5", "jayson": "^3.6.6", "js-sha3": "^0.8.0", + "lossless-json": "^1.0.5", "tweetnacl": "^1.0.3", "util": "^0.12.4" }, diff --git a/sdk/typescript/src/rpc/client.ts b/sdk/typescript/src/rpc/client.ts index 0347fecd2a5de..c361ee82ec44d 100644 --- a/sdk/typescript/src/rpc/client.ts +++ b/sdk/typescript/src/rpc/client.ts @@ -4,6 +4,7 @@ import RpcClient from 'jayson/lib/client/browser'; import fetch from 'cross-fetch'; import { isErrorResponse, isValidResponse } from './client.guard'; +const LosslessJSON = require('lossless-json'); /** * An object defining headers to be passed to the RPC server @@ -45,8 +46,14 @@ export class JsonRpcClient { try { let res: Response = await fetch(url, options); const text = await res.text(); + const result = JSON.stringify(LosslessJSON.parse(text, (key : string, value : any) => { + if (key === "balance") return value.toString(); + if (value.isLosslessNumber) return value.valueOf(); + return value; + } + )); if (res.ok) { - callback(null, text); + callback(null, result); } else { callback(new Error(`${res.status} ${res.statusText}: ${text}`)); } diff --git a/sdk/typescript/src/types/framework.ts b/sdk/typescript/src/types/framework.ts index 4631ecf8b6279..56d6b763ea6de 100644 --- a/sdk/typescript/src/types/framework.ts +++ b/sdk/typescript/src/types/framework.ts @@ -23,4 +23,8 @@ export class Coin { const balance = getObjectFields(data)?.balance; return new BN.BN(balance, 10); } + + static getZero(): BN { + return new BN.BN("0", 10); + } } diff --git a/sdk/typescript/yarn.lock b/sdk/typescript/yarn.lock index b130eb1ecb30b..bccc4dd3cd0e8 100644 --- a/sdk/typescript/yarn.lock +++ b/sdk/typescript/yarn.lock @@ -5341,6 +5341,11 @@ loose-envify@^1.4.0: dependencies: js-tokens "^3.0.0 || ^4.0.0" +lossless-json@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/lossless-json/-/lossless-json-1.0.5.tgz#26df1d7d52543a994df07f1b174cf5576fb1482b" + integrity sha512-RicKUuLwZVNZ6ZdJHgIZnSeA05p8qWc5NW0uR96mpPIjN9WDLUg9+kj1esQU1GkPn9iLZVKatSQK5gyiaFHgJA== + lower-case@^2.0.2: version "2.0.2" resolved "https://registry.npmjs.org/lower-case/-/lower-case-2.0.2.tgz"