Skip to content

TokenRecord.Amount is uint64 but the column is NUMERIC(78,0), silently truncating token amounts on write #2021

Description

@adecaro

Summary

TokenRecord.Amount is declared uint64 (token/services/storage/db/driver/token.go:50-51):

// Amount is the Quantity converted to decimal
Amount uint64

while the backing column is amount NUMERIC(78, 0) NOT NULL (token/services/storage/db/sql/common/tokens.go:1394-1424) — 78 decimal digits, chosen to hold a 256-bit value. uint64 tops out at 20 digits.

The narrowing happens in the production write path, AppendToken (token/services/tokens/storage.go:210):

Amount: q.ToBigInt().Uint64(),

big.Int.Uint64() is documented as undefined when the value does not fit in 64 bits — it returns the low 64 bits with no error and no panic.

Consequences

For any token whose quantity exceeds 2^64-1, the amount column silently receives a wrapped value while the authoritative hex quantity column stays correct. The two columns then disagree, with no error raised at write time.

Quantities above uint64 are representable elsewhere in the stack: token.ToQuantity (token/token/quantity.go:67-85) returns a BigQuantity backed by big.Int whenever precision is not 64, so a driver configured with a larger precision can legitimately produce such values.

Today the damage is limited because no query path reads amount — the selector reads the hex quantity column instead (token/services/storage/db/sql/common/tokens.go:392-406). That also means the corruption is currently invisible.

Why this needs fixing before amount-aware selection

Any work that filters, orders or range-scans on amount (see the companion issue on the missing index) makes this column load-bearing. A wrapped amount would then produce incorrect selections — a large token appearing as a small one — rather than merely sitting unread.

Suggested direction

Widen the field to *big.Int (or a decimal string) and drop the Uint64() conversion, so the stored value matches the column's declared range and the hex quantity. Worth checking whether any consumer of TokenRecord.Amount relies on the uint64 type, and adding a round-trip test with a value above 2^64-1 to lock the behaviour in.

Related: #2017.

Metadata

Metadata

Assignees

Labels

Type

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions