Skip to content

test(sdk): pin toBaseUnits behaviour at the i128 and NUMERIC(39,0) boundaries - #40

Open
jdjioe5-cpu wants to merge 1 commit into
Kinlock-Org:mainfrom
jdjioe5-cpu:test/amount-precision-boundary
Open

jdjioe5-cpu wants to merge 1 commit into
Kinlock-Org:mainfrom
jdjioe5-cpu:test/amount-precision-boundary

Conversation

@jdjioe5-cpu

Copy link
Copy Markdown

What and why

Closes #36.
Pins the amount precision and boundary behaviour across the SDK and database write paths at the i128 and NUMERIC(39,0) limits per roadmap row M2-21. Characterises as-is behaviour of toBaseUnits and fromBaseUnits at 2^127 - 1, 2^127, 39 digits, 40 digits, and i128::MAX / 10^7 human amounts without rounding, and verifies Postgres numeric overflow behaviour on the write path.

Hard-rule and doc review:

  • docs/ARCHITECTURE_ESSENTIALS.md §4 (USDC via SAC, 7 decimals, i128) and §7 (Amounts are NUMERIC(39,0) and bigint in code)
  • AGENTS.md §3 (Hard rules 10: Minimum viable; 11: Country-agnostic core)
  • AGENTS.md §8.2 (kinlock-sdk: amounts are bigint in code, decimal strings in JSON, NUMERIC(39,0) in Postgres; never use number for money; no public API additions without approval)

Changes

  • packages/sdk/src/__tests__/format.test.ts: added boundary characterisation suite covering 2^127 - 1, 2^127, 39-digit NUMERIC(39,0) boundary, 40-digit overflow, i128::MAX / 10^7 plain integer amounts, negative inputs, and sub-unit fraction rejection.
  • services/indexer/test/ingest.test.ts: added write-path test proving a 39-digit amount (10^39 - 1) succeeds within NUMERIC(39,0), while a 40-digit amount (10^39) is rejected on the write path with Postgres numeric field overflow (less than 10^39, SQLSTATE 22003).
  • ROADMAP.md: marked M2-21 as DONE, added Changelog entry, and updated Last updated.

As-Is Boundary Characterisation Table (from actual test run)

Input / Condition Formatted String Base Units (bigint) fromBaseUnits Stack Behaviour
2^127 - 1 (i128::MAX) "17014118346046923173168730371588.4105727" 170141183460469231731687303715884105727n "17014118346046923173168730371588.4105727" Accepted across SDK, contract i128, and Postgres NUMERIC(39,0). Exact round-trip.
2^127 (one above i128::MAX) "17014118346046923173168730371588.4105728" 170141183460469231731687303715884105728n "17014118346046923173168730371588.4105728" Accepted by SDK pure BigInt; rejected on-chain by Soroban host / contract with Overflow (code 50 / XDR i128 range failure).
10^39 - 1 (39 digits) "99999999999999999999999999999999.9999999" 999999999999999999999999999999999999999n "99999999999999999999999999999999.9999999" Accepted by SDK; fits Postgres NUMERIC(39,0) column on write path.
10^39 (40 digits) "100000000000000000000000000000000" 1000000000000000000000000000000000000000n "100000000000000000000000000000000" Accepted by SDK pure BigInt; rejected on DB write path with Postgres numeric field overflow (less than 10^39, error code 22003).
i128::MAX / 10^7 (plain integer human amount) "17014118346046923173168730371588" 170141183460469231731687303715880000000n "17014118346046923173168730371588" Accepted by SDK (<= i128::MAX).
One above i128::MAX / 10^7 "17014118346046923173168730371589" 170141183460469231731687303715890000000n "17014118346046923173168730371589" Accepted by SDK pure BigInt (> i128::MAX); rejected by contract i128 overflow.
Negative input "-1", "-0.1" N/A N/A Rejected by SDK toBaseUnits with INVALID_AMOUNT. fromBaseUnits formats negative delta ("-0.00001").
Fraction > asset decimals "0.00000001" (8 decimals) N/A N/A Rejected by SDK with INVALID_AMOUNT ("amounts are never rounded").

Hard rules / invariants touched

  • None (diff stays strictly within scope: test suites in SDK & indexer, plus ROADMAP.md).
  • Follows Hard rules 10 (Minimum viable: no unnecessary guards without consensus) and 11 (Country-agnostic core).
  • Zero instances of number type on any money path.

Tests

Commands executed locally and results:

pnpm lint
> biome check .
Checked 73 files in 23ms. No fixes applied.

pnpm typecheck
> pnpm -r typecheck
packages/sdk typecheck: Done
services/indexer typecheck: Done

pnpm test
> vitest run
Test Files  15 passed (15)
Tests  173 passed (173)

pnpm build
> pnpm -r build
packages/sdk build: Done
services/indexer build: Done

Docs / ADR

None needed (no public API changes).

Roadmap

  • M2-21: TODO -> DONE
  • Changelog entry added: yes

Assumptions and open questions

@jdjioe5-cpu
jdjioe5-cpu force-pushed the test/amount-precision-boundary branch from 09f4c52 to 3ee9ace Compare October 17, 2026 14:07
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.

Pin the amount precision boundary: what toBaseUnits refuses at the i128 and NUMERIC(39,0) limits

2 participants