Skip to content

Fix raw balance precision loss in sumSingleBalance#219

Open
azeemshaik025 wants to merge 1 commit into
DefiLlama:masterfrom
azeemshaik025:fix/sum-single-balance-bigint-precision
Open

Fix raw balance precision loss in sumSingleBalance#219
azeemshaik025 wants to merge 1 commit into
DefiLlama:masterfrom
azeemshaik025:fix/sum-single-balance-bigint-precision

Conversation

@azeemshaik025
Copy link
Copy Markdown

What

sumSingleBalance currently coerces aggregated BigInt totals back through JavaScript Number before storing them as strings. For raw uint256 token balances above Number.MAX_SAFE_INTEGER (~9e15) this either rounds to the nearest float or emits scientific notation, both of which corrupt the stored balance.

     const prevBalance = convertToBigInt(balances[token]);
     const value = (prevBalance + convertToBigInt(balance))
-    isValidNumber(Number(value))
-    balances[token] = Number(value).toString()
+    isValidNumber(value);
+    balances[token] = value.toString();

The number-valued path (USD / coingecko amounts) is intentionally untouched.

Why it matters

The path ChainApi.add / ChainApi.addGasTokenBalances.addsumSingleBalance is the standard route adapters use, and many of them pass raw uint256 BigInts or integer strings:

  • projects/shmonad/index.js, projects/obol/index.js, projects/neopin-liquid-staking/index.js call api.addGasToken(<BigInt>)
  • projects/lombard-vault/index.js calls api.add(token, <BigInt>)
  • projects/blueberry/v2.js, projects/dtrinity, projects/kvants, projects/sharky call api.add(token, '<raw uint256 string>')

With the old code these get silently corrupted:

9007199254740993            → 9007199254740992
1000000000000000001         → 1000000000000000000
1731174581703269000170      → 1.731174581703269e+21
1234567890123456789012345   → 1.2345678901234568e+24

Once a value is stored as a scientific-notation string, downstream normalizeBalances throws:

SyntaxError: Cannot convert 1.731174581703269e+21 to a BigInt

The pre-existing Indexer - getLogs with processor test in src/util/indexer.test.ts was asserting these scientific-notation outputs as the expected values (e.g. '2.0821579300721433e+27', '1e+21', '8.9999999e+21'). That snapshot is direct evidence the corruption was reaching real adapter output via event log processing.

Tests

Added 6 tests in src/generalUtil.test.ts covering public seams:

  • sumSingleBalance preserves unsafe integer strings exactly
  • Balances.add preserves unsafe integer strings through chain-token balances
  • ChainApi preserves exact raw integer balances returned by adapters
  • normalizeBalances accepts gas-token balances produced by ChainApi
  • sumSingleBalance preserves exact BigInt aggregation beyond Number precision
  • sumSingleBalance keeps number-valued balances on the number path (regression guard so the USD / decimal path is untouched)

RED/GREEN verified: reverting just the implementation change produces 5 failures including the normalizeBalances BigInt SyntaxError. Restoring the fix gives 19/19 pass.

Snapshot update

The 12 expected balance values in Indexer - getLogs with processor were encoding the bug. Replaced them with structural assertions (decimal-integer string, positive BigInt) plus a comment, because the exact on-chain integers can't be recovered from the previously rounded scientific-notation strings without a credentialed indexer run. Can rebake exact values if a maintainer with an indexer endpoint runs the test, or that can happen as a follow-up.

Verification

  • npm run test-only -- src/generalUtil.test.ts --runTestsByPath → 19/19 pass
  • tsc --noEmit → clean
  • indexer.test.ts requires LLAMA_INDEXER_V2_ENDPOINT / LLAMA_INDEXER_V2_API_KEY, so the structural assertions were not executed locally. Type-checks clean and the assertions are direct expressions of the property the fix guarantees.

sumSingleBalance was coercing aggregated BigInt totals back through
JavaScript Number before storing them as strings. For raw uint256
balances above Number.MAX_SAFE_INTEGER this either rounds to the
nearest float or emits scientific notation, both of which corrupt
the stored balance and break downstream normalizeBalances when it
calls BigInt() on the result.

Store BigInt totals as exact decimal strings instead. The number-
valued path (USD / coingecko amounts) is intentionally untouched.

Add public-seam regression tests through ChainApi, Balances, and
normalizeBalances in src/generalUtil.test.ts.

Update the existing "Indexer - getLogs with processor" snapshot in
src/util/indexer.test.ts, which was asserting scientific-notation
and Number-rounded outputs as expected. Replaced with structural
assertions (decimal-integer string, positive BigInt) since exact
on-chain integers can't be recovered from the corrupted strings
without a credentialed indexer run.
@azeemshaik025 azeemshaik025 marked this pull request as ready for review May 13, 2026 06:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant