feat: Fixed Contract Values Cannot Be Converted Into Readable JSON - #359
Merged
Conversation
There was a problem hiding this comment.
Pull request overview
Adds a new decoder that converts Soroban ScVal values into readable serde_json::Value output (including recursive structures and large integers), and wires it into the decode module exports so downstream tooling can consume JSON rather than XDR/debug strings.
Changes:
- Introduces
scval_to_jsonwith an exhaustiveScValvariant mapping, recursive handling forVec/Map, and a depth guard. - Implements decimal string rendering for 128/256-bit integers and hex rendering for
Bytes. - Exposes the new converter via
crates/core/src/decode/mod.rs.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
| crates/core/src/decode/scval_to_json.rs | New recursive ScVal → JSON conversion + unit tests (depth guard, containers, big-int rendering, address/error/instance shapes). |
| crates/core/src/decode/mod.rs | Registers the new module and re-exports scval_to_json from crate::decode. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comment on lines
+69
to
+77
| ScVal::Map(Some(entries)) => { | ||
| let mut obj = Map::with_capacity(entries.len()); | ||
| for entry in entries.iter() { | ||
| let key = scval_key_to_string(&entry.key, depth + 1); | ||
| let value = convert(&entry.val, depth + 1); | ||
| obj.insert(key, value); | ||
| } | ||
| Value::Object(obj) | ||
| } |
Comment on lines
+86
to
+90
| /// Renders a map key as a JSON object key. Keys that convert to a plain JSON | ||
| /// string (symbols, strings, addresses, ...) are used as-is; everything else | ||
| /// (numbers, bools, nested containers) falls back to its compact JSON text so | ||
| /// the map can still be losslessly represented as a `serde_json::Value::Object`. | ||
| fn scval_key_to_string(key: &ScVal, depth: usize) -> String { |
Comment on lines
+125
to
+133
| Some(entries) => { | ||
| let mut obj = Map::with_capacity(entries.len()); | ||
| for entry in entries.iter() { | ||
| let key = scval_key_to_string(&entry.key, depth + 1); | ||
| let value = convert(&entry.val, depth + 1); | ||
| obj.insert(key, value); | ||
| } | ||
| Value::Object(obj) | ||
| } |
codeZe-us
self-requested a review
July 18, 2026 15:28
Contributor
|
@Juwonlo please fix workflow issues |
Distinct ScVal keys (e.g. U32(7) vs String("7")) could previously
stringify to the same JSON object key, silently overwriting entries.
Detect collisions and fall back to a lossless {key, value} entries
array in that case; applies to both ScVal::Map and
ContractInstance.storage. Also fixes rustfmt violations.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Contributor
Author
|
@codeZe-us Fixed the workflow issues |
codeZe-us
approved these changes
Jul 19, 2026
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.
Summary [ Fixes #331 ]
Adds a recursive
SCVal→serde_json::Valueconverter so Soroban contract values (arguments, storage entries, return values) can be rendered as readable JSON instead of raw XDR or RustDebugdumps, for use by the React app and VSCode extension.New:
crates/core/src/decode/scval_to_json.rspub fn scval_to_json(&ScVal) -> serde_json::Value— exhaustive match over all 22ScValvariants (no wildcard arm, so a futurestellar-xdrbump that adds a variant fails to compile rather than silently dropping data).Bool,Void,U32/I32/U64/I64,Timepoint,Duration) map directly tojson!(val).Symbol/Stringare UTF-8 decoded (lossy) into JSON strings.Vec/Maprecurse into JSONArray/Object; map keys are rendered as JSON strings (native string keys pass through, non-string keys — e.g. numeric or nested — fall back to their compact JSON text) so aMap<ScVal, ScVal>can be losslessly represented as a JSON object.U128/I128/U256/I256render as decimal strings, not JSON numbers, since 128/256-bit integers can't be represented exactly in JSON/JS numbers.U256/I256decimal conversion is hand-rolled (schoolbook long division on 4×u64limbs) since there's no bignum dependency in this crate.Bytes→0x-prefixed hex string;Address→ strkey (reuses the existingscaddress_to_strkeyhelper fromauth.rsrather than reimplementing it).ContractInstance,Error,LedgerKeyContractInstance,LedgerKeyNonceall get sensible nested-object representations.MAX_SCVAL_DEPTH = 100. Beyond that, a node is replaced with{"__truncated__": true, "reason": ...}instead of recursing further — this is the mitigation for maliciously/corruptly nested payloads (e.g. 10,000-deepVec) that could otherwise stack-overflow the converter, since XDR decoding elsewhere in this crate (Limits::none()) does not itself cap nesting depth.Vec/Map,Vec(None)vsVec(Some([]))distinction, address strkey rendering,Error/ledger-key variants,ContractInstance, and big-integer round-trips foru128/i128/u256/i256verified against known constants (2²⁵⁶−1, −2²⁵⁵, 2¹²⁸−1, −2¹²⁷, plus mixed-limb values). Also includes a depth-truncation test that builds a 150-deep nestedVecand asserts the truncation marker is hit without overflowing the stack.Modified:
crates/core/src/decode/mod.rspub mod scval_to_json;and re-exportedscval_to_jsonatcrate::decode::scval_to_json, following the existing pattern for other decode submodules.Test plan
stellar-xdr v21.2.0source