fix: handle u64 integer precision loss across Rust-JS boundary#66
Merged
fix: handle u64 integer precision loss across Rust-JS boundary#66
Conversation
Add serde_utils module with flexible deserializers that accept both string and numeric JSON representations of integers. Transform u64 values exceeding Number.MAX_SAFE_INTEGER to strings server-side before sending to JavaScript clients. Update the interpreter VM and resolver to return proper numeric JSON values instead of pre-stringifying u64s. The code generator now emits deserialize_with attributes for integer fields, and ore types are updated accordingly.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Greptile SummaryThis PR fixes u64 integer precision loss across the Rust-to-JavaScript boundary by introducing a Key changes:
Issue found:
Confidence Score: 4/5
Important Files Changed
Sequence DiagramsequenceDiagram
participant BC as Blockchain
participant VM as Interpreter VM
participant PR as Projector
participant TR as transform_large_u64_to_strings
participant WS as WebSocket Server
participant JS as JavaScript Client
participant SDK as Rust SDK Client
BC->>VM: Account/instruction update (raw bytes)
VM->>VM: U64FromLeBytes / U64FromBeBytes → Value::Number(u64)
VM->>VM: SlotHashResolver → Value::Number(rng as u64)
VM->>PR: Mutation batch (Value::Number for u64 fields)
PR->>TR: transform_large_u64_to_strings(frame.data)
TR->>TR: Walk JSON tree recursively
TR->>TR: n > MAX_SAFE_INTEGER (2^53-1) → Value::String
TR->>TR: n < MIN_SAFE_INTEGER (-(2^53-1)) → Value::String
PR->>WS: Frame with large u64s as strings
WS->>JS: JSON over WebSocket (strings for large integers)
WS->>SDK: JSON over WebSocket (strings for large integers)
SDK->>SDK: serde_utils::deserialize_option_u64 / deserialize_option_option_u64
SDK->>SDK: Accepts both number and string representations
Prompt To Fix All With AIThis is a comment left during a code review.
Path: rust/hyperstack-sdk/src/serde_utils.rs
Line: 200-202
Comment:
**`visit_f64` in `deserialize_option_option_i64` has no bounds check**
Unlike every other `visit_f64` in this file — including `I64OrStringVisitor::visit_f64` (lines 61-67) which guards with `v >= i64::MIN as f64 && v <= i64::MAX as f64`, and `deserialize_option_option_u64`'s visitor (lines 165-171) which guards with `v >= 0.0 && v < (u64::MAX as f64)` — this implementation does an unchecked `v as i64` cast with zero range validation.
In Rust, casting an out-of-range `f64` to `i64` saturates silently: `1e30_f64 as i64 == i64::MAX`, `f64::INFINITY as i64 == i64::MAX`, and `f64::NAN as i64 == 0`. This means corrupt or unexpected float payloads will produce wrong integer values rather than returning a deserialization error.
```suggestion
fn visit_f64<E: de::Error>(self, v: f64) -> Result<Option<Option<i64>>, E> {
if v >= i64::MIN as f64 && v < (i64::MAX as f64) {
Ok(Some(Some(v as i64)))
} else {
Err(E::custom(format!("f64 {v} out of i64 range")))
}
}
```
Note: the upper bound uses strict `<` rather than `<=` because `i64::MAX as f64` rounds up to `2^63` (one value above `i64::MAX`), so `<=` would admit that out-of-range value.
How can I resolve this? If you propose a fix, please make it concise.Last reviewed commit: "chore: remove dead e..." |
Live state-mode and list-mode bus updates bypassed transform_large_u64_to_strings, causing JavaScript clients to receive corrupted u64 values after the initial snapshot. Apply the transform upstream in the projector before serialization so all downstream consumers receive pre-transformed data.
…ec deserializers - Add bounds check to I64OrStringVisitor::visit_f64 to reject values outside i64 range instead of silently saturating - Tighten U64OrStringVisitor::visit_f64 upper bound (use < instead of <= to reject f64 values rounded up beyond u64::MAX) - Add deserialize_option_vec_i64 and deserialize_option_option_vec_i64 required by codegen for i64/timestamp array fields
…in WebSocket frames
deserialize_with_for_type mapped i32 fields to deserialize_option_i64 and u32 fields to deserialize_option_u64, causing type-mismatch compile errors since the field types are Option<i32>/Option<u32>. Fix int_kind to emit the exact type suffix (i32/u32/i64/u64) and add thin 32-bit wrapper functions to serde_utils that delegate to the 64-bit deserializers and narrow via TryFrom, rejecting out-of-range values.
The 13 hardcoded extract_field! invocations created local variables that were never referenced — all generated expression code accesses fields through state.get()/computed_cache.get(), not bare identifiers. Also adds #[allow(clippy::too_many_arguments)] to the generated eval function.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Add serde_utils module with flexible deserializers that accept both string and numeric JSON representations of integers. Transform u64 values exceeding Number.MAX_SAFE_INTEGER to strings server-side before sending to JavaScript clients. Update the interpreter VM and resolver to return proper numeric JSON values instead of pre-stringifying u64s. The code generator now emits deserialize_with attributes for integer fields, and ore types are updated accordingly.