Skip to content

feat: Fixed Contract Values Cannot Be Converted Into Readable JSON - #359

Merged
codeZe-us merged 2 commits into
Toolbox-Lab:mainfrom
Juwonlo:issue_331
Jul 19, 2026
Merged

feat: Fixed Contract Values Cannot Be Converted Into Readable JSON#359
codeZe-us merged 2 commits into
Toolbox-Lab:mainfrom
Juwonlo:issue_331

Conversation

@Juwonlo

@Juwonlo Juwonlo commented Jul 18, 2026

Copy link
Copy Markdown
Contributor

Summary [ Fixes #331 ]

Adds a recursive SCValserde_json::Value converter so Soroban contract values (arguments, storage entries, return values) can be rendered as readable JSON instead of raw XDR or Rust Debug dumps, for use by the React app and VSCode extension.

  • New: crates/core/src/decode/scval_to_json.rs

    • pub fn scval_to_json(&ScVal) -> serde_json::Value — exhaustive match over all 22 ScVal variants (no wildcard arm, so a future stellar-xdr bump that adds a variant fails to compile rather than silently dropping data).
    • Primitives (Bool, Void, U32/I32/U64/I64, Timepoint, Duration) map directly to json!(val).
    • Symbol/String are UTF-8 decoded (lossy) into JSON strings.
    • Vec/Map recurse into JSON Array/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 a Map<ScVal, ScVal> can be losslessly represented as a JSON object.
    • U128/I128/U256/I256 render as decimal strings, not JSON numbers, since 128/256-bit integers can't be represented exactly in JSON/JS numbers. U256/I256 decimal conversion is hand-rolled (schoolbook long division on 4×u64 limbs) since there's no bignum dependency in this crate.
    • Bytes0x-prefixed hex string; Address → strkey (reuses the existing scaddress_to_strkey helper from auth.rs rather than reimplementing it).
    • ContractInstance, Error, LedgerKeyContractInstance, LedgerKeyNonce all get sensible nested-object representations.
    • Depth guard: recursion is capped at 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-deep Vec) that could otherwise stack-overflow the converter, since XDR decoding elsewhere in this crate (Limits::none()) does not itself cap nesting depth.
    • Unit tests cover: all primitive mappings, UTF-8 symbol/string decoding, nested Vec/Map, Vec(None) vs Vec(Some([])) distinction, address strkey rendering, Error/ledger-key variants, ContractInstance, and big-integer round-trips for u128/i128/u256/i256 verified against known constants (2²⁵⁶−1, −2²⁵⁵, 2¹²⁸−1, −2¹²⁷, plus mixed-limb values). Also includes a depth-truncation test that builds a 150-deep nested Vec and asserts the truncation marker is hit without overflowing the stack.
  • Modified: crates/core/src/decode/mod.rs

    • Registered pub mod scval_to_json; and re-exported scval_to_json at crate::decode::scval_to_json, following the existing pattern for other decode submodules.

Test plan

  • Manually verified all XDR type shapes against stellar-xdr v21.2.0 source
  • Manually verified big-integer decimal conversions against independently computed constants

Copilot AI review requested due to automatic review settings July 18, 2026 06:21

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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_json with an exhaustive ScVal variant mapping, recursive handling for Vec/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 thread crates/core/src/decode/scval_to_json.rs Outdated
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 thread crates/core/src/decode/scval_to_json.rs Outdated
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 thread crates/core/src/decode/scval_to_json.rs Outdated
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
codeZe-us self-requested a review July 18, 2026 15:28
@codeZe-us

Copy link
Copy Markdown
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>
@Juwonlo

Juwonlo commented Jul 18, 2026

Copy link
Copy Markdown
Contributor Author

@codeZe-us Fixed the workflow issues

@codeZe-us
codeZe-us merged commit 77161c6 into Toolbox-Lab:main Jul 19, 2026
2 checks passed
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.

Task 26: Contract Values Cannot Be Converted Into Readable JSON for Debugging

3 participants