diff --git a/cmd/crates/soroban-spec-tools/src/lib.rs b/cmd/crates/soroban-spec-tools/src/lib.rs index 6b2cb74c3..7899f6093 100644 --- a/cmd/crates/soroban-spec-tools/src/lib.rs +++ b/cmd/crates/soroban-spec-tools/src/lib.rs @@ -585,6 +585,11 @@ impl Spec { (val, ScType::Result(inner)) => self.xdr_to_json(val, &inner.ok_type)?, (val, ScType::Option(inner)) => self.xdr_to_json(val, &inner.value_type)?, + + // ScType::Val is the generic Soroban value type that can hold any ScVal. + // Delegate to to_json which handles all ScVal variants without type info. + (val, ScType::Val) => to_json(val)?, + (ScVal::Map(Some(_)) | ScVal::Vec(Some(_)) | ScVal::U32(_), type_) => { self.sc_object_to_json(val, type_)? } @@ -2450,4 +2455,63 @@ mod tests { let json = spec.sc_map_to_json(&sc_map, &map_type).unwrap(); assert_eq!(json.to_string(), r#"{"foo":2}"#); } + + #[test] + fn test_xdr_to_json_bytes_with_val_type() { + // Regression test for https://github.com/stellar/stellar-cli/issues/2469 + // When a contract function returns ScType::Val and the runtime value is + // ScVal::Bytes (e.g. BytesN<32>), xdr_to_json should succeed instead of + // panicking with "doesn't have a matching Val". + let spec = Spec(None); + let bytes_val = ScVal::Bytes(ScBytes( + vec![ + 0x05, 0x5e, 0xf8, 0x16, 0x22, 0x3e, 0xe5, 0x21, 0x6b, 0x18, 0xc2, 0xdf, 0x00, 0xd6, + 0x15, 0xee, 0x08, 0x9a, 0x8e, 0xf1, 0x1b, 0x92, 0x7a, 0x76, 0x4e, 0x4f, 0x5d, 0x6c, + 0x0c, 0xb4, 0xf4, 0xc7, + ] + .try_into() + .unwrap(), + )); + let result = spec.xdr_to_json(&bytes_val, &ScType::Val); + assert_eq!( + result.unwrap(), + Value::String( + "055ef816223ee5216b18c2df00d615ee089a8ef11b927a764e4f5d6c0cb4f4c7".to_string() + ) + ); + } + + #[test] + fn test_xdr_to_json_map_with_val_type() { + // ScVal::Map with ScType::Val should delegate to to_json, not sc_object_to_json. + let spec = Spec(None); + let map_val = ScVal::Map(Some( + ScMap::sorted_from(vec![ScMapEntry { + key: ScVal::Symbol(ScSymbol("key".try_into().unwrap())), + val: ScVal::U32(42), + }]) + .unwrap(), + )); + let result = spec.xdr_to_json(&map_val, &ScType::Val); + assert!(result.is_ok(), "Map with ScType::Val should not error"); + } + + #[test] + fn test_xdr_to_json_vec_with_val_type() { + // ScVal::Vec with ScType::Val should delegate to to_json, not sc_object_to_json. + let spec = Spec(None); + let vec_val = ScVal::Vec(Some( + ScVec::try_from(vec![ScVal::U32(1), ScVal::U32(2)]).unwrap(), + )); + let result = spec.xdr_to_json(&vec_val, &ScType::Val); + assert!(result.is_ok(), "Vec with ScType::Val should not error"); + } + + #[test] + fn test_xdr_to_json_u32_with_val_type() { + // ScVal::U32 with ScType::Val should delegate to to_json, not sc_object_to_json. + let spec = Spec(None); + let result = spec.xdr_to_json(&ScVal::U32(100), &ScType::Val); + assert_eq!(result.unwrap(), Value::Number(100.into())); + } }