From 50eb2251156a836a4f001dc3007d0d6cbb63c073 Mon Sep 17 00:00:00 2001 From: Leigh <351529+leighmcculloch@users.noreply.github.com> Date: Thu, 3 Sep 2026 15:16:09 +0000 Subject: [PATCH 01/10] patch stellar-xdr to pr 566 and add udt name limit constant --- Cargo.lock | 3 +-- Cargo.toml | 5 +++++ cmd/crates/soroban-spec-tools/src/contract.rs | 2 +- cmd/crates/soroban-spec-tools/src/lib.rs | 16 +++++++++++++--- .../src/commands/contract/arg_parsing.rs | 14 +++++++++----- deny.toml | 7 ++++++- 6 files changed, 35 insertions(+), 12 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f2799a7b69..95a15ee2da 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5882,8 +5882,7 @@ dependencies = [ [[package]] name = "stellar-xdr" version = "28.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f93d09ff8b9f919b084f664003c4c546ac66a76affd5429460dbe29f4b326f8e" +source = "git+https://github.com/stellar/rs-stellar-xdr?rev=99a031a6f6a0935f68c073705651bd1addca1fd4#99a031a6f6a0935f68c073705651bd1addca1fd4" dependencies = [ "arbitrary", "base64 0.22.1", diff --git a/Cargo.toml b/Cargo.toml index 023d794096..4feda4e124 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -121,6 +121,11 @@ testcontainers = "0.27.2" httpmock = "0.7.0" astral-tokio-tar = "0.6.0" +[patch.crates-io] +# Pending https://github.com/stellar/rs-stellar-xdr/pull/566, which widens the +# user-defined type name limit so that a fully qualified type name fits. +stellar-xdr = { git = "https://github.com/stellar/rs-stellar-xdr", rev = "99a031a6f6a0935f68c073705651bd1addca1fd4" } + [profile.release] overflow-checks = true diff --git a/cmd/crates/soroban-spec-tools/src/contract.rs b/cmd/crates/soroban-spec-tools/src/contract.rs index bf877dccc1..89ef225fd6 100644 --- a/cmd/crates/soroban-spec-tools/src/contract.rs +++ b/cmd/crates/soroban-spec-tools/src/contract.rs @@ -316,7 +316,7 @@ fn indent(s: &str, n: usize) -> String { .join("\n") } -fn format_name(lib: &StringM<80>, name: &StringM<60>) -> String { +fn format_name(lib: &StringM<80>, name: &StringM<{ crate::UDT_NAME_LIMIT }>) -> String { if lib.is_empty() { sanitize(&name.to_utf8_string_lossy()) } else { diff --git a/cmd/crates/soroban-spec-tools/src/lib.rs b/cmd/crates/soroban-spec-tools/src/lib.rs index bccf21828a..396f55a57f 100644 --- a/cmd/crates/soroban-spec-tools/src/lib.rs +++ b/cmd/crates/soroban-spec-tools/src/lib.rs @@ -12,7 +12,7 @@ use stellar_xdr::{ ScSpecTypeTuple, ScSpecTypeUdt, ScSpecTypeVec, ScSpecUdtEnumV0, ScSpecUdtErrorEnumCaseV0, ScSpecUdtErrorEnumV0, ScSpecUdtStructV0, ScSpecUdtUnionCaseTupleV0, ScSpecUdtUnionCaseV0, ScSpecUdtUnionCaseVoidV0, ScSpecUdtUnionV0, ScString, ScSymbol, ScVal, ScVec, StringM, - UInt128Parts, UInt256Parts, Uint256, VecM, + UInt128Parts, UInt256Parts, Uint256, VecM, SC_SPEC_TYPE_NAME_LIMIT, }; pub mod contract; @@ -25,6 +25,12 @@ pub mod wasm; pub use contract::sanitize; pub use verify::SpecWarning; +/// The XDR limit on the length of a user-defined type name +/// ([`SC_SPEC_TYPE_NAME_LIMIT`]), as the `u32` that `StringM`'s const generic +/// expects. +#[allow(clippy::cast_possible_truncation)] +pub const UDT_NAME_LIMIT: u32 = SC_SPEC_TYPE_NAME_LIMIT as u32; + #[derive(thiserror::Error, Debug)] pub enum Error { #[error("an unknown error occurred")] @@ -372,7 +378,7 @@ impl Spec { Ok(val) } - fn parse_udt(&self, name: &StringM<60>, value: &Value) -> Result { + fn parse_udt(&self, name: &StringM, value: &Value) -> Result { let name = &name.to_utf8_string_lossy(); match (self.find(name)?, value) { (ScSpecEntry::UdtStructV0(strukt), Value::Object(map)) => { @@ -649,7 +655,11 @@ impl Spec { /// # Panics /// /// May panic - pub fn udt_to_json(&self, name: &StringM<60>, sc_obj: &ScVal) -> Result { + pub fn udt_to_json( + &self, + name: &StringM, + sc_obj: &ScVal, + ) -> Result { let name = &name.to_utf8_string_lossy(); let udt = self.find(name)?; Ok(match (sc_obj, udt) { diff --git a/cmd/soroban-cli/src/commands/contract/arg_parsing.rs b/cmd/soroban-cli/src/commands/contract/arg_parsing.rs index c97cfa551f..6ffdb34436 100644 --- a/cmd/soroban-cli/src/commands/contract/arg_parsing.rs +++ b/cmd/soroban-cli/src/commands/contract/arg_parsing.rs @@ -1074,7 +1074,8 @@ mod tests { }; // Build a minimal Spec with a union type: enum MyEnum { Unit } - let union_name: StringM<60> = "MyEnum".try_into().unwrap(); + let union_name: StringM<{ soroban_spec_tools::UDT_NAME_LIMIT }> = + "MyEnum".try_into().unwrap(); let case_name: StringM<60> = "Unit".try_into().unwrap(); let spec = Spec(Some(vec![ScSpecEntry::UdtUnionV0(ScSpecUdtUnionV0 { doc: StringM::default(), @@ -1123,7 +1124,8 @@ mod tests { ScSpecUdtUnionCaseV0, ScSpecUdtUnionCaseVoidV0, ScSpecUdtUnionV0, StringM, }; - let union_name: StringM<60> = "MyEnum".try_into().unwrap(); + let union_name: StringM<{ soroban_spec_tools::UDT_NAME_LIMIT }> = + "MyEnum".try_into().unwrap(); let spec = Spec(Some(vec![ScSpecEntry::UdtUnionV0(ScSpecUdtUnionV0 { doc: StringM::default(), lib: StringM::default(), @@ -1190,7 +1192,7 @@ mod tests { use stellar_xdr::{ ScSpecEntry, ScSpecTypeUdt, ScSpecUdtStructFieldV0, ScSpecUdtStructV0, StringM, }; - let struct_name: StringM<60> = name.try_into().unwrap(); + let struct_name: StringM<{ soroban_spec_tools::UDT_NAME_LIMIT }> = name.try_into().unwrap(); let fields_xdr: Vec = fields .iter() .map(|(n, t)| ScSpecUdtStructFieldV0 { @@ -1324,7 +1326,8 @@ mod tests { ScSpecUdtUnionV0, StringM, }; - let union_name: StringM<60> = "Choice".try_into().unwrap(); + let union_name: StringM<{ soroban_spec_tools::UDT_NAME_LIMIT }> = + "Choice".try_into().unwrap(); let spec = Spec(Some(vec![ScSpecEntry::UdtUnionV0(ScSpecUdtUnionV0 { doc: StringM::default(), lib: StringM::default(), @@ -1362,7 +1365,8 @@ mod tests { ScSpecUdtUnionV0, StringM, }; - let union_name: StringM<60> = "OneOf".try_into().unwrap(); + let union_name: StringM<{ soroban_spec_tools::UDT_NAME_LIMIT }> = + "OneOf".try_into().unwrap(); let spec = Spec(Some(vec![ScSpecEntry::UdtUnionV0(ScSpecUdtUnionV0 { doc: StringM::default(), lib: StringM::default(), diff --git a/deny.toml b/deny.toml index dd05ae3b5b..7dfb9622f7 100644 --- a/deny.toml +++ b/deny.toml @@ -231,7 +231,12 @@ allow-registry = ["https://github.com/rust-lang/crates.io-index"] # List of URLs for allowed Git repositories allow-git = [ # Only used by the unpublished doc-gen crate, temporarily until PR is merged: https://github.com/ConnorGray/clap-markdown/pull/48 - "https://github.com/ConnorGray/clap-markdown?rev=42956b342cef3325d9060fc43995d595e7c8aa66" + "https://github.com/ConnorGray/clap-markdown?rev=42956b342cef3325d9060fc43995d595e7c8aa66", + + # Temporary patch, remove once merged and released: + # https://github.com/stellar/rs-stellar-xdr/pull/566, which widens the + # user-defined type name limit so a fully qualified type name fits. + "https://github.com/stellar/rs-stellar-xdr?rev=99a031a6f6a0935f68c073705651bd1addca1fd4", ] [sources.allow-org] From 69c41b9f884567e5b977293ea62b588f93d1240b Mon Sep 17 00:00:00 2001 From: Leigh <351529+leighmcculloch@users.noreply.github.com> Date: Thu, 3 Sep 2026 15:16:39 +0000 Subject: [PATCH 02/10] reduce qualified udt names in contract info output --- cmd/crates/soroban-spec-tools/src/lib.rs | 1 + cmd/crates/soroban-spec-tools/src/reduce.rs | 465 ++++++++++++++++++ .../src/commands/contract/info/interface.rs | 34 +- 3 files changed, 497 insertions(+), 3 deletions(-) create mode 100644 cmd/crates/soroban-spec-tools/src/reduce.rs diff --git a/cmd/crates/soroban-spec-tools/src/lib.rs b/cmd/crates/soroban-spec-tools/src/lib.rs index 396f55a57f..a11f1a9137 100644 --- a/cmd/crates/soroban-spec-tools/src/lib.rs +++ b/cmd/crates/soroban-spec-tools/src/lib.rs @@ -17,6 +17,7 @@ use stellar_xdr::{ pub mod contract; pub mod event; +pub mod reduce; pub mod test_utils; pub mod utils; mod verify; diff --git a/cmd/crates/soroban-spec-tools/src/reduce.rs b/cmd/crates/soroban-spec-tools/src/reduce.rs new file mode 100644 index 0000000000..0c0de1beb4 --- /dev/null +++ b/cmd/crates/soroban-spec-tools/src/reduce.rs @@ -0,0 +1,465 @@ +//! Reduce the fully qualified user-defined type names that appear in a contract +//! spec down to short, human-friendly names for display and code generation. +//! +//! Contract specs built with a soroban-sdk that names user-defined types by +//! their fully qualified Rust path (see +//! ) carry names such as +//! `my_contract::inner::State`. Those names are unambiguous but noisy, and the +//! `::` separator is not a valid identifier, so the Rust and TypeScript binding +//! generators cannot use them verbatim. This module rewrites every user-defined +//! type name — both where the type is declared and everywhere it is referenced +//! — to its final path segment (`State`). When two distinct qualified names +//! reduce to the same short name they are disambiguated with a numeric suffix +//! (`State`, `State1`), and the collision is reported so a caller can warn. + +use std::collections::{BTreeMap, HashSet}; + +use stellar_xdr::{ + ScSpecEntry, ScSpecEventV0, ScSpecFunctionV0, ScSpecTypeDef, ScSpecTypeMap, ScSpecTypeOption, + ScSpecTypeResult, ScSpecTypeTuple, ScSpecTypeUdt, ScSpecTypeVec, ScSpecUdtEnumV0, + ScSpecUdtErrorEnumV0, ScSpecUdtStructV0, ScSpecUdtUnionCaseV0, ScSpecUdtUnionV0, +}; + +/// A single user-defined type name that was rewritten during reduction. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Rename { + /// The original, fully qualified name (e.g. `my_contract::inner::State`). + pub from: String, + /// The reduced name it was rewritten to (e.g. `State` or `State1`). + pub to: String, +} + +/// A group of distinct qualified names that share the same short name and so +/// had to be disambiguated with numeric suffixes. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Collision { + /// The short name they all reduced to before suffixing (e.g. `State`). + pub short: String, + /// The colliding members, each with the suffixed name it received. + pub members: Vec, +} + +/// The record of what `reduce_udt_names` changed. +#[derive(Debug, Clone, Default, PartialEq, Eq)] +pub struct Reduction { + /// Every type whose name changed, in declaration order. + pub renames: Vec, + /// The subset of renames that were forced to a numeric suffix because + /// another qualified type reduced to the same short name. + pub collisions: Vec, +} + +impl Reduction { + /// Whether any name was rewritten. + #[must_use] + pub fn is_empty(&self) -> bool { + self.renames.is_empty() + } +} + +/// The final path segment of a qualified name, i.e. the part after the last +/// `::`. Names without a `::` are returned unchanged. +fn short_name(name: &str) -> &str { + name.rsplit("::").next().unwrap_or(name) +} + +/// Reduce the fully qualified user-defined type names in `spec` to short names, +/// returning the rewritten spec alongside a report of what changed. A spec that +/// already uses short names (no `::`) is returned unchanged with an empty +/// [`Reduction`]. +#[must_use] +pub fn reduce_udt_names(spec: &[ScSpecEntry]) -> (Vec, Reduction) { + let declared = declared_udt_names(spec); + let (map, reduction) = build_mapping(&declared); + + let reduced = spec.iter().map(|e| rewrite_entry(e, &map)).collect(); + (reduced, reduction) +} + +/// The names of every user-defined type declared in the spec, in declaration +/// order and de-duplicated. +fn declared_udt_names(spec: &[ScSpecEntry]) -> Vec { + let mut names = Vec::new(); + let mut seen = HashSet::new(); + let mut push = |name: String| { + if seen.insert(name.clone()) { + names.push(name); + } + }; + for entry in spec { + match entry { + ScSpecEntry::UdtStructV0(ScSpecUdtStructV0 { name, .. }) + | ScSpecEntry::UdtUnionV0(ScSpecUdtUnionV0 { name, .. }) + | ScSpecEntry::UdtEnumV0(ScSpecUdtEnumV0 { name, .. }) + | ScSpecEntry::UdtErrorEnumV0(ScSpecUdtErrorEnumV0 { name, .. }) => { + push(name.to_utf8_string_lossy()); + } + ScSpecEntry::FunctionV0(_) | ScSpecEntry::EventV0(_) => {} + } + } + names +} + +/// Build the rename map from qualified name to reduced name, grouping by short +/// name so collisions can be disambiguated deterministically. +fn build_mapping(declared: &[String]) -> (BTreeMap, Reduction) { + // Group the declared names by their short name. `BTreeMap`/sorted members + // keep suffix assignment stable across runs. + let mut groups: BTreeMap<&str, Vec<&String>> = BTreeMap::new(); + for name in declared { + groups.entry(short_name(name)).or_default().push(name); + } + for members in groups.values_mut() { + members.sort(); + } + + let mut map = BTreeMap::new(); + let mut renames = Vec::new(); + let mut collisions = Vec::new(); + // Every reduced name handed out, so a numeric suffix never lands on a name + // already taken by another (possibly unrelated) type. + let mut used: HashSet = HashSet::new(); + + for (short, members) in &groups { + let collision = members.len() > 1; + let mut collision_members = Vec::new(); + for (i, full) in members.iter().enumerate() { + let mut n = i; + let mut candidate = if i == 0 { + (*short).to_string() + } else { + format!("{short}{n}") + }; + while used.contains(&candidate) { + n += 1; + candidate = format!("{short}{n}"); + } + used.insert(candidate.clone()); + map.insert((*full).clone(), candidate.clone()); + if **full != candidate { + let rename = Rename { + from: (*full).clone(), + to: candidate.clone(), + }; + renames.push(rename.clone()); + if collision { + collision_members.push(rename); + } + } else if collision { + collision_members.push(Rename { + from: (*full).clone(), + to: candidate, + }); + } + } + if collision { + collisions.push(Collision { + short: (*short).to_string(), + members: collision_members, + }); + } + } + + // Report renames in the spec's declaration order rather than sorted order. + renames.sort_by_key(|r| { + declared + .iter() + .position(|d| *d == r.from) + .unwrap_or(usize::MAX) + }); + + ( + map, + Reduction { + renames, + collisions, + }, + ) +} + +/// Look up the reduced name for a referenced type. A reference to a declared +/// type resolves through the map; a stray qualified name that was never +/// declared still gets its `::` stripped so no invalid identifier leaks into +/// the output. +fn reduced_ref(name: &str, map: &BTreeMap) -> String { + map.get(name) + .cloned() + .unwrap_or_else(|| short_name(name).to_string()) +} + +fn rewrite_entry(entry: &ScSpecEntry, map: &BTreeMap) -> ScSpecEntry { + match entry { + ScSpecEntry::UdtStructV0(s) => { + let mut s = s.clone(); + s.name = rename_udt(&s.name.to_utf8_string_lossy(), map); + s.fields = s + .fields + .iter() + .map(|f| { + let mut f = f.clone(); + f.type_ = rewrite_type(&f.type_, map); + f + }) + .collect::>() + .try_into() + .unwrap_or(s.fields); + ScSpecEntry::UdtStructV0(s) + } + ScSpecEntry::UdtUnionV0(u) => { + let mut u = u.clone(); + u.name = rename_udt(&u.name.to_utf8_string_lossy(), map); + u.cases = u + .cases + .iter() + .map(|c| rewrite_union_case(c, map)) + .collect::>() + .try_into() + .unwrap_or(u.cases); + ScSpecEntry::UdtUnionV0(u) + } + ScSpecEntry::UdtEnumV0(e) => { + let mut e = e.clone(); + e.name = rename_udt(&e.name.to_utf8_string_lossy(), map); + ScSpecEntry::UdtEnumV0(e) + } + ScSpecEntry::UdtErrorEnumV0(e) => { + let mut e = e.clone(); + e.name = rename_udt(&e.name.to_utf8_string_lossy(), map); + ScSpecEntry::UdtErrorEnumV0(e) + } + ScSpecEntry::FunctionV0(f) => { + let ScSpecFunctionV0 { + doc, + name, + inputs, + outputs, + } = f; + let mut f = ScSpecFunctionV0 { + doc: doc.clone(), + name: name.clone(), + inputs: inputs.clone(), + outputs: outputs.clone(), + }; + f.inputs = f + .inputs + .iter() + .map(|i| { + let mut i = i.clone(); + i.type_ = rewrite_type(&i.type_, map); + i + }) + .collect::>() + .try_into() + .unwrap_or(f.inputs); + f.outputs = f + .outputs + .iter() + .map(|o| rewrite_type(o, map)) + .collect::>() + .try_into() + .unwrap_or(f.outputs); + ScSpecEntry::FunctionV0(f) + } + ScSpecEntry::EventV0(e) => { + let ScSpecEventV0 { .. } = e; + let mut e = e.clone(); + e.params = e + .params + .iter() + .map(|p| { + let mut p = p.clone(); + p.type_ = rewrite_type(&p.type_, map); + p + }) + .collect::>() + .try_into() + .unwrap_or(e.params); + ScSpecEntry::EventV0(e) + } + } +} + +fn rewrite_union_case( + case: &ScSpecUdtUnionCaseV0, + map: &BTreeMap, +) -> ScSpecUdtUnionCaseV0 { + match case { + ScSpecUdtUnionCaseV0::VoidV0(_) => case.clone(), + ScSpecUdtUnionCaseV0::TupleV0(t) => { + let mut t = t.clone(); + t.type_ = t + .type_ + .iter() + .map(|ty| rewrite_type(ty, map)) + .collect::>() + .try_into() + .unwrap_or(t.type_); + ScSpecUdtUnionCaseV0::TupleV0(t) + } + } +} + +/// Rewrite a type, recursing into every place a user-defined type can be +/// referenced. +fn rewrite_type(ty: &ScSpecTypeDef, map: &BTreeMap) -> ScSpecTypeDef { + match ty { + ScSpecTypeDef::Udt(ScSpecTypeUdt { name }) => ScSpecTypeDef::Udt(ScSpecTypeUdt { + name: rename_udt(&name.to_utf8_string_lossy(), map), + }), + ScSpecTypeDef::Option(o) => ScSpecTypeDef::Option(Box::new(ScSpecTypeOption { + value_type: Box::new(rewrite_type(&o.value_type, map)), + })), + ScSpecTypeDef::Result(r) => ScSpecTypeDef::Result(Box::new(ScSpecTypeResult { + ok_type: Box::new(rewrite_type(&r.ok_type, map)), + error_type: Box::new(rewrite_type(&r.error_type, map)), + })), + ScSpecTypeDef::Vec(v) => ScSpecTypeDef::Vec(Box::new(ScSpecTypeVec { + element_type: Box::new(rewrite_type(&v.element_type, map)), + })), + ScSpecTypeDef::Map(m) => ScSpecTypeDef::Map(Box::new(ScSpecTypeMap { + key_type: Box::new(rewrite_type(&m.key_type, map)), + value_type: Box::new(rewrite_type(&m.value_type, map)), + })), + ScSpecTypeDef::Tuple(t) => ScSpecTypeDef::Tuple(Box::new(ScSpecTypeTuple { + value_types: t + .value_types + .iter() + .map(|vt| rewrite_type(vt, map)) + .collect::>() + .try_into() + .unwrap_or_else(|_| t.value_types.clone()), + })), + other => other.clone(), + } +} + +/// Rewrite a declaration or reference name to its reduced form as a +/// `StringM<256>`. The reduced name is never longer than the original, so it +/// always fits. +fn rename_udt( + name: &str, + map: &BTreeMap, +) -> stellar_xdr::StringM<{ crate::UDT_NAME_LIMIT }> { + reduced_ref(name, map) + .try_into() + .unwrap_or_else(|_| name.try_into().unwrap_or_default()) +} + +#[cfg(test)] +mod tests { + use super::*; + use stellar_xdr::{ + ScSpecFunctionInputV0, ScSpecTypeUdt, ScSpecUdtEnumV0, ScSpecUdtStructFieldV0, + ScSpecUdtStructV0, StringM, VecM, + }; + + fn udt(name: &str) -> ScSpecTypeDef { + ScSpecTypeDef::Udt(ScSpecTypeUdt { + name: name.try_into().unwrap(), + }) + } + + fn struct_entry(name: &str, field_type: ScSpecTypeDef) -> ScSpecEntry { + ScSpecEntry::UdtStructV0(ScSpecUdtStructV0 { + doc: StringM::default(), + lib: StringM::default(), + name: name.try_into().unwrap(), + fields: vec![ScSpecUdtStructFieldV0 { + doc: StringM::default(), + name: "f".try_into().unwrap(), + type_: field_type, + }] + .try_into() + .unwrap(), + }) + } + + fn enum_entry(name: &str) -> ScSpecEntry { + ScSpecEntry::UdtEnumV0(ScSpecUdtEnumV0 { + doc: StringM::default(), + lib: StringM::default(), + name: name.try_into().unwrap(), + cases: VecM::default(), + }) + } + + fn fn_entry(name: &str, input_type: ScSpecTypeDef) -> ScSpecEntry { + ScSpecEntry::FunctionV0(ScSpecFunctionV0 { + doc: StringM::default(), + name: name.try_into().unwrap(), + inputs: vec![ScSpecFunctionInputV0 { + doc: StringM::default(), + name: "arg".try_into().unwrap(), + type_: input_type, + }] + .try_into() + .unwrap(), + outputs: VecM::default(), + }) + } + + fn entry_name(entry: &ScSpecEntry) -> String { + match entry { + ScSpecEntry::UdtStructV0(s) => s.name.to_utf8_string_lossy(), + ScSpecEntry::UdtUnionV0(u) => u.name.to_utf8_string_lossy(), + ScSpecEntry::UdtEnumV0(e) => e.name.to_utf8_string_lossy(), + ScSpecEntry::UdtErrorEnumV0(e) => e.name.to_utf8_string_lossy(), + _ => String::new(), + } + } + + #[test] + fn no_qualified_names_is_a_noop() { + let spec = vec![enum_entry("State"), struct_entry("Point", udt("State"))]; + let (reduced, report) = reduce_udt_names(&spec); + assert!(report.is_empty()); + assert_eq!(reduced, spec); + } + + #[test] + fn shortens_and_rewrites_references() { + let spec = vec![ + enum_entry("my_contract::inner::State"), + fn_entry("run", udt("my_contract::inner::State")), + ]; + let (reduced, report) = reduce_udt_names(&spec); + + assert_eq!(entry_name(&reduced[0]), "State"); + // The reference inside the function input is rewritten too. + let ScSpecEntry::FunctionV0(f) = &reduced[1] else { + panic!("expected function") + }; + assert_eq!(f.inputs[0].type_, udt("State")); + + assert_eq!(report.renames.len(), 1); + assert_eq!(report.renames[0].from, "my_contract::inner::State"); + assert_eq!(report.renames[0].to, "State"); + assert!(report.collisions.is_empty()); + } + + #[test] + fn disambiguates_collisions_with_suffixes() { + let spec = vec![ + enum_entry("my_contract::a::Status"), + enum_entry("my_contract::b::Status"), + fn_entry("run", udt("my_contract::b::Status")), + ]; + let (reduced, report) = reduce_udt_names(&spec); + + // Deterministic: `a::Status` sorts first and keeps the bare name. + assert_eq!(entry_name(&reduced[0]), "Status"); + assert_eq!(entry_name(&reduced[1]), "Status1"); + // The reference to `b::Status` follows its rename to `Status1`. + let ScSpecEntry::FunctionV0(f) = &reduced[2] else { + panic!("expected function") + }; + assert_eq!(f.inputs[0].type_, udt("Status1")); + + assert_eq!(report.collisions.len(), 1); + let collision = &report.collisions[0]; + assert_eq!(collision.short, "Status"); + assert_eq!(collision.members.len(), 2); + assert_eq!(collision.members[0].to, "Status"); + assert_eq!(collision.members[1].to, "Status1"); + } +} diff --git a/cmd/soroban-cli/src/commands/contract/info/interface.rs b/cmd/soroban-cli/src/commands/contract/info/interface.rs index a2dfa1de38..7a5f16c1b9 100644 --- a/cmd/soroban-cli/src/commands/contract/info/interface.rs +++ b/cmd/soroban-cli/src/commands/contract/info/interface.rs @@ -65,17 +65,45 @@ impl Cmd { } }; + // Contract specs may name user-defined types by their fully qualified + // path (e.g. `my_contract::inner::State`). Those names are noisy and, + // because `::` is not a valid identifier, the Rust and JSON renderers + // below cannot use them as-is. Reduce them to short names for display, + // reporting what changed. The `XdrBase64` output is the canonical + // on-chain spec, so it is left untouched. + let (reduced_spec, reduction) = soroban_spec_tools::reduce::reduce_udt_names(&spec); + if !matches!(self.output, InfoOutput::XdrBase64) { + for rename in &reduction.renames { + print.infoln(format!( + "Reduced type name {} to {}", + rename.from, rename.to + )); + } + if !reduction.collisions.is_empty() { + use std::fmt::Write as _; + let mut msg = String::from( + "Reduced type names collided and were disambiguated with a numeric suffix:", + ); + for collision in &reduction.collisions { + for member in &collision.members { + let _ = write!(msg, "\n {} -> {}", member.from, member.to); + } + } + print.warnln(msg); + } + } + let res = match self.output { InfoOutput::XdrBase64 => base64, - InfoOutput::Json => serde_json::to_string(&spec)?, - InfoOutput::JsonFormatted => serde_json::to_string_pretty(&spec)?, + InfoOutput::Json => serde_json::to_string(&reduced_spec)?, + InfoOutput::JsonFormatted => serde_json::to_string_pretty(&reduced_spec)?, // soroban_spec_rust drops doc strings entirely (rustdocs can execute // code) and routes every spec name through `format_ident!`, which // rejects non-identifier bytes. If a future revision starts // emitting spec strings as `Literal::string` or rustdocs, this // path becomes a terminal-escape-injection vector and must be // sanitized before printing. - InfoOutput::Rust => soroban_spec_rust::generate_without_file(&spec)? + InfoOutput::Rust => soroban_spec_rust::generate_without_file(&reduced_spec)? .to_formatted_string() .expect("Unexpected spec format error"), }; From 4f5ad60802a370007156ae896b60796096c33c28 Mon Sep 17 00:00:00 2001 From: Leigh <351529+leighmcculloch@users.noreply.github.com> Date: Thu, 3 Sep 2026 21:26:41 +0000 Subject: [PATCH 03/10] patch soroban-sdk to pr 1970 and matching xdr rev --- Cargo.lock | 30 ++++++++-------------- Cargo.toml | 13 ++++++++-- cmd/crates/soroban-spec-tools/src/event.rs | 2 +- cmd/crates/soroban-spec-tools/src/lib.rs | 2 +- deny.toml | 7 +++-- 5 files changed, 29 insertions(+), 25 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 95a15ee2da..66f6900c5b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5535,8 +5535,7 @@ version = "28.0.0" [[package]] name = "soroban-ledger-snapshot" version = "28.0.0-rc.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e68b020e9838df2affe8488998943b81d183c7ea4990691a863b39c13d3516e" +source = "git+https://github.com/stellar/rs-soroban-sdk?rev=cd0aa803c5c072987cedefe9c2152e093fde91a5#cd0aa803c5c072987cedefe9c2152e093fde91a5" dependencies = [ "serde", "serde_json", @@ -5549,8 +5548,7 @@ dependencies = [ [[package]] name = "soroban-sdk" version = "28.0.0-rc.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "864a530b8a1eb07c364f3f4b2f069b737ae87df203fd290baebddb9119f2dfe5" +source = "git+https://github.com/stellar/rs-soroban-sdk?rev=cd0aa803c5c072987cedefe9c2152e093fde91a5#cd0aa803c5c072987cedefe9c2152e093fde91a5" dependencies = [ "arbitrary", "bytes-lit", @@ -5566,15 +5564,16 @@ dependencies = [ "soroban-env-host", "soroban-ledger-snapshot", "soroban-sdk-macros", + "soroban-spec", "stellar-strkey 0.0.16", + "stellar-xdr", "visibility", ] [[package]] name = "soroban-sdk-macros" version = "28.0.0-rc.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d66c2bfe91c8ec2179dcab5284ee99215d9271fed2eb8b9819bcd45001ac861f" +source = "git+https://github.com/stellar/rs-soroban-sdk?rev=cd0aa803c5c072987cedefe9c2152e093fde91a5#cd0aa803c5c072987cedefe9c2152e093fde91a5" dependencies = [ "darling 0.20.11", "heck 0.5.0", @@ -5584,7 +5583,6 @@ dependencies = [ "quote", "sha2 0.10.9", "soroban-env-common", - "soroban-spec", "soroban-spec-rust", "stellar-xdr", "syn 2.0.118", @@ -5593,11 +5591,9 @@ dependencies = [ [[package]] name = "soroban-spec" version = "28.0.0-rc.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f20e3c44fa7939fece5c8988efa7c9e062ab5596ae7497035a2bc59ffd48816" +source = "git+https://github.com/stellar/rs-soroban-sdk?rev=cd0aa803c5c072987cedefe9c2152e093fde91a5#cd0aa803c5c072987cedefe9c2152e093fde91a5" dependencies = [ "base64 0.22.1", - "sha2 0.10.9", "stellar-xdr", "thiserror 1.0.69", "wasmparser 0.116.1", @@ -5606,8 +5602,7 @@ dependencies = [ [[package]] name = "soroban-spec-rust" version = "28.0.0-rc.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43eb9293928c35e949818f83fe5eacfe0cf45ebb32552304aaea66279b7c8403" +source = "git+https://github.com/stellar/rs-soroban-sdk?rev=cd0aa803c5c072987cedefe9c2152e093fde91a5#cd0aa803c5c072987cedefe9c2152e093fde91a5" dependencies = [ "prettyplease", "proc-macro2", @@ -5705,8 +5700,7 @@ dependencies = [ [[package]] name = "soroban-token-sdk" version = "28.0.0-rc.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7053541ed1cf2cbfc78625d2b619c496c0748d20d75aee019b2f68169386f4c" +source = "git+https://github.com/stellar/rs-soroban-sdk?rev=cd0aa803c5c072987cedefe9c2152e093fde91a5#cd0aa803c5c072987cedefe9c2152e093fde91a5" dependencies = [ "soroban-sdk", ] @@ -5714,8 +5708,7 @@ dependencies = [ [[package]] name = "soroban-token-spec" version = "28.0.0-rc.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27fe6acb049550f221fcb294c8ca3ede0e89609ea3d480c63b84b8cf4197c416" +source = "git+https://github.com/stellar/rs-soroban-sdk?rev=cd0aa803c5c072987cedefe9c2152e093fde91a5#cd0aa803c5c072987cedefe9c2152e093fde91a5" dependencies = [ "soroban-sdk", "soroban-token-sdk", @@ -5765,8 +5758,7 @@ checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" [[package]] name = "stellar-asset-spec" version = "28.0.0-rc.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4194d1b54a5337c277a0656c2d4da940439f9eb3f2e17a919bc686a870429250" +source = "git+https://github.com/stellar/rs-soroban-sdk?rev=cd0aa803c5c072987cedefe9c2152e093fde91a5#cd0aa803c5c072987cedefe9c2152e093fde91a5" dependencies = [ "soroban-sdk", "soroban-token-sdk", @@ -5882,7 +5874,7 @@ dependencies = [ [[package]] name = "stellar-xdr" version = "28.0.0" -source = "git+https://github.com/stellar/rs-stellar-xdr?rev=99a031a6f6a0935f68c073705651bd1addca1fd4#99a031a6f6a0935f68c073705651bd1addca1fd4" +source = "git+https://github.com/stellar/rs-stellar-xdr?rev=82677e1dfe49b6b94fc6440d9c58be74d2a33bff#82677e1dfe49b6b94fc6440d9c58be74d2a33bff" dependencies = [ "arbitrary", "base64 0.22.1", diff --git a/Cargo.toml b/Cargo.toml index 4feda4e124..36f0bacc46 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -123,8 +123,17 @@ astral-tokio-tar = "0.6.0" [patch.crates-io] # Pending https://github.com/stellar/rs-stellar-xdr/pull/566, which widens the -# user-defined type name limit so that a fully qualified type name fits. -stellar-xdr = { git = "https://github.com/stellar/rs-stellar-xdr", rev = "99a031a6f6a0935f68c073705651bd1addca1fd4" } +# user-defined type name limit so that a fully qualified type name fits. Pinned +# to the same rev rs-soroban-sdk#1970 uses so the graph has one stellar-xdr. +stellar-xdr = { git = "https://github.com/stellar/rs-stellar-xdr", rev = "82677e1dfe49b6b94fc6440d9c58be74d2a33bff" } +# Pending https://github.com/stellar/rs-soroban-sdk/pull/1970, which names +# user-defined types in contract specs by their fully qualified path. +soroban-spec = { git = "https://github.com/stellar/rs-soroban-sdk", rev = "cd0aa803c5c072987cedefe9c2152e093fde91a5" } +soroban-spec-rust = { git = "https://github.com/stellar/rs-soroban-sdk", rev = "cd0aa803c5c072987cedefe9c2152e093fde91a5" } +soroban-sdk = { git = "https://github.com/stellar/rs-soroban-sdk", rev = "cd0aa803c5c072987cedefe9c2152e093fde91a5" } +soroban-token-sdk = { git = "https://github.com/stellar/rs-soroban-sdk", rev = "cd0aa803c5c072987cedefe9c2152e093fde91a5" } +stellar-asset-spec = { git = "https://github.com/stellar/rs-soroban-sdk", rev = "cd0aa803c5c072987cedefe9c2152e093fde91a5" } +soroban-ledger-snapshot = { git = "https://github.com/stellar/rs-soroban-sdk", rev = "cd0aa803c5c072987cedefe9c2152e093fde91a5" } [profile.release] overflow-checks = true diff --git a/cmd/crates/soroban-spec-tools/src/event.rs b/cmd/crates/soroban-spec-tools/src/event.rs index 496cdb1ccf..3cd5fc1df6 100644 --- a/cmd/crates/soroban-spec-tools/src/event.rs +++ b/cmd/crates/soroban-spec-tools/src/event.rs @@ -294,7 +294,7 @@ mod tests { ScSpecEventV0 { doc: StringM::default(), lib: StringM::default(), - name: make_symbol(name), + name: name.try_into().unwrap(), prefix_topics: prefix_topics .into_iter() .map(make_symbol) diff --git a/cmd/crates/soroban-spec-tools/src/lib.rs b/cmd/crates/soroban-spec-tools/src/lib.rs index a11f1a9137..3a5bf5dde9 100644 --- a/cmd/crates/soroban-spec-tools/src/lib.rs +++ b/cmd/crates/soroban-spec-tools/src/lib.rs @@ -2356,7 +2356,7 @@ mod tests { ScSpecEventV0 { doc: StringM::default(), lib: StringM::default(), - name: ScSymbol(name.try_into().unwrap()), + name: name.try_into().unwrap(), prefix_topics: VecM::default(), params: VecM::default(), data_format: stellar_xdr::ScSpecEventDataFormat::SingleValue, diff --git a/deny.toml b/deny.toml index 7dfb9622f7..cae0ef4ba3 100644 --- a/deny.toml +++ b/deny.toml @@ -233,10 +233,13 @@ allow-git = [ # Only used by the unpublished doc-gen crate, temporarily until PR is merged: https://github.com/ConnorGray/clap-markdown/pull/48 "https://github.com/ConnorGray/clap-markdown?rev=42956b342cef3325d9060fc43995d595e7c8aa66", - # Temporary patch, remove once merged and released: + # Temporary patches, remove once merged and released: # https://github.com/stellar/rs-stellar-xdr/pull/566, which widens the # user-defined type name limit so a fully qualified type name fits. - "https://github.com/stellar/rs-stellar-xdr?rev=99a031a6f6a0935f68c073705651bd1addca1fd4", + "https://github.com/stellar/rs-stellar-xdr?rev=82677e1dfe49b6b94fc6440d9c58be74d2a33bff", + # https://github.com/stellar/rs-soroban-sdk/pull/1970, which names + # user-defined types in contract specs by their fully qualified path. + "https://github.com/stellar/rs-soroban-sdk?rev=cd0aa803c5c072987cedefe9c2152e093fde91a5", ] [sources.allow-org] From d4f51129b242e83528779cbcebc6c9876333a65e Mon Sep 17 00:00:00 2001 From: Leigh <351529+leighmcculloch@users.noreply.github.com> Date: Thu, 3 Sep 2026 22:32:38 +0000 Subject: [PATCH 04/10] bump soroban-sdk pr 1970 patch to head 4d8faeff --- Cargo.lock | 24 ++++++++++++------------ Cargo.toml | 12 ++++++------ deny.toml | 2 +- 3 files changed, 19 insertions(+), 19 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 66f6900c5b..ca7aeac1b4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2034,7 +2034,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "778e2ac28f6c47af28e4907f13ffd1e1ddbd400980a9abd7c8df189bf578a5ad" dependencies = [ "libc", - "windows-sys 0.60.2", + "windows-sys 0.52.0", ] [[package]] @@ -4286,7 +4286,7 @@ dependencies = [ "once_cell", "socket2 0.6.4", "tracing", - "windows-sys 0.60.2", + "windows-sys 0.52.0", ] [[package]] @@ -4675,7 +4675,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys 0.9.4", - "windows-sys 0.60.2", + "windows-sys 0.52.0", ] [[package]] @@ -5535,7 +5535,7 @@ version = "28.0.0" [[package]] name = "soroban-ledger-snapshot" version = "28.0.0-rc.1" -source = "git+https://github.com/stellar/rs-soroban-sdk?rev=cd0aa803c5c072987cedefe9c2152e093fde91a5#cd0aa803c5c072987cedefe9c2152e093fde91a5" +source = "git+https://github.com/stellar/rs-soroban-sdk?rev=4d8faeff872aa7567de6fcf07bb33ae74500ab5a#4d8faeff872aa7567de6fcf07bb33ae74500ab5a" dependencies = [ "serde", "serde_json", @@ -5548,7 +5548,7 @@ dependencies = [ [[package]] name = "soroban-sdk" version = "28.0.0-rc.1" -source = "git+https://github.com/stellar/rs-soroban-sdk?rev=cd0aa803c5c072987cedefe9c2152e093fde91a5#cd0aa803c5c072987cedefe9c2152e093fde91a5" +source = "git+https://github.com/stellar/rs-soroban-sdk?rev=4d8faeff872aa7567de6fcf07bb33ae74500ab5a#4d8faeff872aa7567de6fcf07bb33ae74500ab5a" dependencies = [ "arbitrary", "bytes-lit", @@ -5573,7 +5573,7 @@ dependencies = [ [[package]] name = "soroban-sdk-macros" version = "28.0.0-rc.1" -source = "git+https://github.com/stellar/rs-soroban-sdk?rev=cd0aa803c5c072987cedefe9c2152e093fde91a5#cd0aa803c5c072987cedefe9c2152e093fde91a5" +source = "git+https://github.com/stellar/rs-soroban-sdk?rev=4d8faeff872aa7567de6fcf07bb33ae74500ab5a#4d8faeff872aa7567de6fcf07bb33ae74500ab5a" dependencies = [ "darling 0.20.11", "heck 0.5.0", @@ -5591,7 +5591,7 @@ dependencies = [ [[package]] name = "soroban-spec" version = "28.0.0-rc.1" -source = "git+https://github.com/stellar/rs-soroban-sdk?rev=cd0aa803c5c072987cedefe9c2152e093fde91a5#cd0aa803c5c072987cedefe9c2152e093fde91a5" +source = "git+https://github.com/stellar/rs-soroban-sdk?rev=4d8faeff872aa7567de6fcf07bb33ae74500ab5a#4d8faeff872aa7567de6fcf07bb33ae74500ab5a" dependencies = [ "base64 0.22.1", "stellar-xdr", @@ -5602,7 +5602,7 @@ dependencies = [ [[package]] name = "soroban-spec-rust" version = "28.0.0-rc.1" -source = "git+https://github.com/stellar/rs-soroban-sdk?rev=cd0aa803c5c072987cedefe9c2152e093fde91a5#cd0aa803c5c072987cedefe9c2152e093fde91a5" +source = "git+https://github.com/stellar/rs-soroban-sdk?rev=4d8faeff872aa7567de6fcf07bb33ae74500ab5a#4d8faeff872aa7567de6fcf07bb33ae74500ab5a" dependencies = [ "prettyplease", "proc-macro2", @@ -5700,7 +5700,7 @@ dependencies = [ [[package]] name = "soroban-token-sdk" version = "28.0.0-rc.1" -source = "git+https://github.com/stellar/rs-soroban-sdk?rev=cd0aa803c5c072987cedefe9c2152e093fde91a5#cd0aa803c5c072987cedefe9c2152e093fde91a5" +source = "git+https://github.com/stellar/rs-soroban-sdk?rev=4d8faeff872aa7567de6fcf07bb33ae74500ab5a#4d8faeff872aa7567de6fcf07bb33ae74500ab5a" dependencies = [ "soroban-sdk", ] @@ -5708,7 +5708,7 @@ dependencies = [ [[package]] name = "soroban-token-spec" version = "28.0.0-rc.1" -source = "git+https://github.com/stellar/rs-soroban-sdk?rev=cd0aa803c5c072987cedefe9c2152e093fde91a5#cd0aa803c5c072987cedefe9c2152e093fde91a5" +source = "git+https://github.com/stellar/rs-soroban-sdk?rev=4d8faeff872aa7567de6fcf07bb33ae74500ab5a#4d8faeff872aa7567de6fcf07bb33ae74500ab5a" dependencies = [ "soroban-sdk", "soroban-token-sdk", @@ -5758,7 +5758,7 @@ checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" [[package]] name = "stellar-asset-spec" version = "28.0.0-rc.1" -source = "git+https://github.com/stellar/rs-soroban-sdk?rev=cd0aa803c5c072987cedefe9c2152e093fde91a5#cd0aa803c5c072987cedefe9c2152e093fde91a5" +source = "git+https://github.com/stellar/rs-soroban-sdk?rev=4d8faeff872aa7567de6fcf07bb33ae74500ab5a#4d8faeff872aa7567de6fcf07bb33ae74500ab5a" dependencies = [ "soroban-sdk", "soroban-token-sdk", @@ -6062,7 +6062,7 @@ dependencies = [ "getrandom 0.3.3", "once_cell", "rustix 1.0.8", - "windows-sys 0.60.2", + "windows-sys 0.52.0", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 36f0bacc46..20f1a8d1c6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -128,12 +128,12 @@ astral-tokio-tar = "0.6.0" stellar-xdr = { git = "https://github.com/stellar/rs-stellar-xdr", rev = "82677e1dfe49b6b94fc6440d9c58be74d2a33bff" } # Pending https://github.com/stellar/rs-soroban-sdk/pull/1970, which names # user-defined types in contract specs by their fully qualified path. -soroban-spec = { git = "https://github.com/stellar/rs-soroban-sdk", rev = "cd0aa803c5c072987cedefe9c2152e093fde91a5" } -soroban-spec-rust = { git = "https://github.com/stellar/rs-soroban-sdk", rev = "cd0aa803c5c072987cedefe9c2152e093fde91a5" } -soroban-sdk = { git = "https://github.com/stellar/rs-soroban-sdk", rev = "cd0aa803c5c072987cedefe9c2152e093fde91a5" } -soroban-token-sdk = { git = "https://github.com/stellar/rs-soroban-sdk", rev = "cd0aa803c5c072987cedefe9c2152e093fde91a5" } -stellar-asset-spec = { git = "https://github.com/stellar/rs-soroban-sdk", rev = "cd0aa803c5c072987cedefe9c2152e093fde91a5" } -soroban-ledger-snapshot = { git = "https://github.com/stellar/rs-soroban-sdk", rev = "cd0aa803c5c072987cedefe9c2152e093fde91a5" } +soroban-spec = { git = "https://github.com/stellar/rs-soroban-sdk", rev = "4d8faeff872aa7567de6fcf07bb33ae74500ab5a" } +soroban-spec-rust = { git = "https://github.com/stellar/rs-soroban-sdk", rev = "4d8faeff872aa7567de6fcf07bb33ae74500ab5a" } +soroban-sdk = { git = "https://github.com/stellar/rs-soroban-sdk", rev = "4d8faeff872aa7567de6fcf07bb33ae74500ab5a" } +soroban-token-sdk = { git = "https://github.com/stellar/rs-soroban-sdk", rev = "4d8faeff872aa7567de6fcf07bb33ae74500ab5a" } +stellar-asset-spec = { git = "https://github.com/stellar/rs-soroban-sdk", rev = "4d8faeff872aa7567de6fcf07bb33ae74500ab5a" } +soroban-ledger-snapshot = { git = "https://github.com/stellar/rs-soroban-sdk", rev = "4d8faeff872aa7567de6fcf07bb33ae74500ab5a" } [profile.release] overflow-checks = true diff --git a/deny.toml b/deny.toml index cae0ef4ba3..b41c100999 100644 --- a/deny.toml +++ b/deny.toml @@ -239,7 +239,7 @@ allow-git = [ "https://github.com/stellar/rs-stellar-xdr?rev=82677e1dfe49b6b94fc6440d9c58be74d2a33bff", # https://github.com/stellar/rs-soroban-sdk/pull/1970, which names # user-defined types in contract specs by their fully qualified path. - "https://github.com/stellar/rs-soroban-sdk?rev=cd0aa803c5c072987cedefe9c2152e093fde91a5", + "https://github.com/stellar/rs-soroban-sdk?rev=4d8faeff872aa7567de6fcf07bb33ae74500ab5a", ] [sources.allow-org] From b71b72895dac2fe1cac97f365263b3fec98ab425 Mon Sep 17 00:00:00 2001 From: Leigh <351529+leighmcculloch@users.noreply.github.com> Date: Fri, 4 Sep 2026 09:56:01 +0000 Subject: [PATCH 05/10] use soroban-spec reduce instead of cli duplicate --- cmd/crates/soroban-spec-tools/src/lib.rs | 1 - cmd/crates/soroban-spec-tools/src/reduce.rs | 465 ------------------ .../src/commands/contract/info/interface.rs | 22 +- 3 files changed, 14 insertions(+), 474 deletions(-) delete mode 100644 cmd/crates/soroban-spec-tools/src/reduce.rs diff --git a/cmd/crates/soroban-spec-tools/src/lib.rs b/cmd/crates/soroban-spec-tools/src/lib.rs index 3a5bf5dde9..9563c38407 100644 --- a/cmd/crates/soroban-spec-tools/src/lib.rs +++ b/cmd/crates/soroban-spec-tools/src/lib.rs @@ -17,7 +17,6 @@ use stellar_xdr::{ pub mod contract; pub mod event; -pub mod reduce; pub mod test_utils; pub mod utils; mod verify; diff --git a/cmd/crates/soroban-spec-tools/src/reduce.rs b/cmd/crates/soroban-spec-tools/src/reduce.rs deleted file mode 100644 index 0c0de1beb4..0000000000 --- a/cmd/crates/soroban-spec-tools/src/reduce.rs +++ /dev/null @@ -1,465 +0,0 @@ -//! Reduce the fully qualified user-defined type names that appear in a contract -//! spec down to short, human-friendly names for display and code generation. -//! -//! Contract specs built with a soroban-sdk that names user-defined types by -//! their fully qualified Rust path (see -//! ) carry names such as -//! `my_contract::inner::State`. Those names are unambiguous but noisy, and the -//! `::` separator is not a valid identifier, so the Rust and TypeScript binding -//! generators cannot use them verbatim. This module rewrites every user-defined -//! type name — both where the type is declared and everywhere it is referenced -//! — to its final path segment (`State`). When two distinct qualified names -//! reduce to the same short name they are disambiguated with a numeric suffix -//! (`State`, `State1`), and the collision is reported so a caller can warn. - -use std::collections::{BTreeMap, HashSet}; - -use stellar_xdr::{ - ScSpecEntry, ScSpecEventV0, ScSpecFunctionV0, ScSpecTypeDef, ScSpecTypeMap, ScSpecTypeOption, - ScSpecTypeResult, ScSpecTypeTuple, ScSpecTypeUdt, ScSpecTypeVec, ScSpecUdtEnumV0, - ScSpecUdtErrorEnumV0, ScSpecUdtStructV0, ScSpecUdtUnionCaseV0, ScSpecUdtUnionV0, -}; - -/// A single user-defined type name that was rewritten during reduction. -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct Rename { - /// The original, fully qualified name (e.g. `my_contract::inner::State`). - pub from: String, - /// The reduced name it was rewritten to (e.g. `State` or `State1`). - pub to: String, -} - -/// A group of distinct qualified names that share the same short name and so -/// had to be disambiguated with numeric suffixes. -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct Collision { - /// The short name they all reduced to before suffixing (e.g. `State`). - pub short: String, - /// The colliding members, each with the suffixed name it received. - pub members: Vec, -} - -/// The record of what `reduce_udt_names` changed. -#[derive(Debug, Clone, Default, PartialEq, Eq)] -pub struct Reduction { - /// Every type whose name changed, in declaration order. - pub renames: Vec, - /// The subset of renames that were forced to a numeric suffix because - /// another qualified type reduced to the same short name. - pub collisions: Vec, -} - -impl Reduction { - /// Whether any name was rewritten. - #[must_use] - pub fn is_empty(&self) -> bool { - self.renames.is_empty() - } -} - -/// The final path segment of a qualified name, i.e. the part after the last -/// `::`. Names without a `::` are returned unchanged. -fn short_name(name: &str) -> &str { - name.rsplit("::").next().unwrap_or(name) -} - -/// Reduce the fully qualified user-defined type names in `spec` to short names, -/// returning the rewritten spec alongside a report of what changed. A spec that -/// already uses short names (no `::`) is returned unchanged with an empty -/// [`Reduction`]. -#[must_use] -pub fn reduce_udt_names(spec: &[ScSpecEntry]) -> (Vec, Reduction) { - let declared = declared_udt_names(spec); - let (map, reduction) = build_mapping(&declared); - - let reduced = spec.iter().map(|e| rewrite_entry(e, &map)).collect(); - (reduced, reduction) -} - -/// The names of every user-defined type declared in the spec, in declaration -/// order and de-duplicated. -fn declared_udt_names(spec: &[ScSpecEntry]) -> Vec { - let mut names = Vec::new(); - let mut seen = HashSet::new(); - let mut push = |name: String| { - if seen.insert(name.clone()) { - names.push(name); - } - }; - for entry in spec { - match entry { - ScSpecEntry::UdtStructV0(ScSpecUdtStructV0 { name, .. }) - | ScSpecEntry::UdtUnionV0(ScSpecUdtUnionV0 { name, .. }) - | ScSpecEntry::UdtEnumV0(ScSpecUdtEnumV0 { name, .. }) - | ScSpecEntry::UdtErrorEnumV0(ScSpecUdtErrorEnumV0 { name, .. }) => { - push(name.to_utf8_string_lossy()); - } - ScSpecEntry::FunctionV0(_) | ScSpecEntry::EventV0(_) => {} - } - } - names -} - -/// Build the rename map from qualified name to reduced name, grouping by short -/// name so collisions can be disambiguated deterministically. -fn build_mapping(declared: &[String]) -> (BTreeMap, Reduction) { - // Group the declared names by their short name. `BTreeMap`/sorted members - // keep suffix assignment stable across runs. - let mut groups: BTreeMap<&str, Vec<&String>> = BTreeMap::new(); - for name in declared { - groups.entry(short_name(name)).or_default().push(name); - } - for members in groups.values_mut() { - members.sort(); - } - - let mut map = BTreeMap::new(); - let mut renames = Vec::new(); - let mut collisions = Vec::new(); - // Every reduced name handed out, so a numeric suffix never lands on a name - // already taken by another (possibly unrelated) type. - let mut used: HashSet = HashSet::new(); - - for (short, members) in &groups { - let collision = members.len() > 1; - let mut collision_members = Vec::new(); - for (i, full) in members.iter().enumerate() { - let mut n = i; - let mut candidate = if i == 0 { - (*short).to_string() - } else { - format!("{short}{n}") - }; - while used.contains(&candidate) { - n += 1; - candidate = format!("{short}{n}"); - } - used.insert(candidate.clone()); - map.insert((*full).clone(), candidate.clone()); - if **full != candidate { - let rename = Rename { - from: (*full).clone(), - to: candidate.clone(), - }; - renames.push(rename.clone()); - if collision { - collision_members.push(rename); - } - } else if collision { - collision_members.push(Rename { - from: (*full).clone(), - to: candidate, - }); - } - } - if collision { - collisions.push(Collision { - short: (*short).to_string(), - members: collision_members, - }); - } - } - - // Report renames in the spec's declaration order rather than sorted order. - renames.sort_by_key(|r| { - declared - .iter() - .position(|d| *d == r.from) - .unwrap_or(usize::MAX) - }); - - ( - map, - Reduction { - renames, - collisions, - }, - ) -} - -/// Look up the reduced name for a referenced type. A reference to a declared -/// type resolves through the map; a stray qualified name that was never -/// declared still gets its `::` stripped so no invalid identifier leaks into -/// the output. -fn reduced_ref(name: &str, map: &BTreeMap) -> String { - map.get(name) - .cloned() - .unwrap_or_else(|| short_name(name).to_string()) -} - -fn rewrite_entry(entry: &ScSpecEntry, map: &BTreeMap) -> ScSpecEntry { - match entry { - ScSpecEntry::UdtStructV0(s) => { - let mut s = s.clone(); - s.name = rename_udt(&s.name.to_utf8_string_lossy(), map); - s.fields = s - .fields - .iter() - .map(|f| { - let mut f = f.clone(); - f.type_ = rewrite_type(&f.type_, map); - f - }) - .collect::>() - .try_into() - .unwrap_or(s.fields); - ScSpecEntry::UdtStructV0(s) - } - ScSpecEntry::UdtUnionV0(u) => { - let mut u = u.clone(); - u.name = rename_udt(&u.name.to_utf8_string_lossy(), map); - u.cases = u - .cases - .iter() - .map(|c| rewrite_union_case(c, map)) - .collect::>() - .try_into() - .unwrap_or(u.cases); - ScSpecEntry::UdtUnionV0(u) - } - ScSpecEntry::UdtEnumV0(e) => { - let mut e = e.clone(); - e.name = rename_udt(&e.name.to_utf8_string_lossy(), map); - ScSpecEntry::UdtEnumV0(e) - } - ScSpecEntry::UdtErrorEnumV0(e) => { - let mut e = e.clone(); - e.name = rename_udt(&e.name.to_utf8_string_lossy(), map); - ScSpecEntry::UdtErrorEnumV0(e) - } - ScSpecEntry::FunctionV0(f) => { - let ScSpecFunctionV0 { - doc, - name, - inputs, - outputs, - } = f; - let mut f = ScSpecFunctionV0 { - doc: doc.clone(), - name: name.clone(), - inputs: inputs.clone(), - outputs: outputs.clone(), - }; - f.inputs = f - .inputs - .iter() - .map(|i| { - let mut i = i.clone(); - i.type_ = rewrite_type(&i.type_, map); - i - }) - .collect::>() - .try_into() - .unwrap_or(f.inputs); - f.outputs = f - .outputs - .iter() - .map(|o| rewrite_type(o, map)) - .collect::>() - .try_into() - .unwrap_or(f.outputs); - ScSpecEntry::FunctionV0(f) - } - ScSpecEntry::EventV0(e) => { - let ScSpecEventV0 { .. } = e; - let mut e = e.clone(); - e.params = e - .params - .iter() - .map(|p| { - let mut p = p.clone(); - p.type_ = rewrite_type(&p.type_, map); - p - }) - .collect::>() - .try_into() - .unwrap_or(e.params); - ScSpecEntry::EventV0(e) - } - } -} - -fn rewrite_union_case( - case: &ScSpecUdtUnionCaseV0, - map: &BTreeMap, -) -> ScSpecUdtUnionCaseV0 { - match case { - ScSpecUdtUnionCaseV0::VoidV0(_) => case.clone(), - ScSpecUdtUnionCaseV0::TupleV0(t) => { - let mut t = t.clone(); - t.type_ = t - .type_ - .iter() - .map(|ty| rewrite_type(ty, map)) - .collect::>() - .try_into() - .unwrap_or(t.type_); - ScSpecUdtUnionCaseV0::TupleV0(t) - } - } -} - -/// Rewrite a type, recursing into every place a user-defined type can be -/// referenced. -fn rewrite_type(ty: &ScSpecTypeDef, map: &BTreeMap) -> ScSpecTypeDef { - match ty { - ScSpecTypeDef::Udt(ScSpecTypeUdt { name }) => ScSpecTypeDef::Udt(ScSpecTypeUdt { - name: rename_udt(&name.to_utf8_string_lossy(), map), - }), - ScSpecTypeDef::Option(o) => ScSpecTypeDef::Option(Box::new(ScSpecTypeOption { - value_type: Box::new(rewrite_type(&o.value_type, map)), - })), - ScSpecTypeDef::Result(r) => ScSpecTypeDef::Result(Box::new(ScSpecTypeResult { - ok_type: Box::new(rewrite_type(&r.ok_type, map)), - error_type: Box::new(rewrite_type(&r.error_type, map)), - })), - ScSpecTypeDef::Vec(v) => ScSpecTypeDef::Vec(Box::new(ScSpecTypeVec { - element_type: Box::new(rewrite_type(&v.element_type, map)), - })), - ScSpecTypeDef::Map(m) => ScSpecTypeDef::Map(Box::new(ScSpecTypeMap { - key_type: Box::new(rewrite_type(&m.key_type, map)), - value_type: Box::new(rewrite_type(&m.value_type, map)), - })), - ScSpecTypeDef::Tuple(t) => ScSpecTypeDef::Tuple(Box::new(ScSpecTypeTuple { - value_types: t - .value_types - .iter() - .map(|vt| rewrite_type(vt, map)) - .collect::>() - .try_into() - .unwrap_or_else(|_| t.value_types.clone()), - })), - other => other.clone(), - } -} - -/// Rewrite a declaration or reference name to its reduced form as a -/// `StringM<256>`. The reduced name is never longer than the original, so it -/// always fits. -fn rename_udt( - name: &str, - map: &BTreeMap, -) -> stellar_xdr::StringM<{ crate::UDT_NAME_LIMIT }> { - reduced_ref(name, map) - .try_into() - .unwrap_or_else(|_| name.try_into().unwrap_or_default()) -} - -#[cfg(test)] -mod tests { - use super::*; - use stellar_xdr::{ - ScSpecFunctionInputV0, ScSpecTypeUdt, ScSpecUdtEnumV0, ScSpecUdtStructFieldV0, - ScSpecUdtStructV0, StringM, VecM, - }; - - fn udt(name: &str) -> ScSpecTypeDef { - ScSpecTypeDef::Udt(ScSpecTypeUdt { - name: name.try_into().unwrap(), - }) - } - - fn struct_entry(name: &str, field_type: ScSpecTypeDef) -> ScSpecEntry { - ScSpecEntry::UdtStructV0(ScSpecUdtStructV0 { - doc: StringM::default(), - lib: StringM::default(), - name: name.try_into().unwrap(), - fields: vec![ScSpecUdtStructFieldV0 { - doc: StringM::default(), - name: "f".try_into().unwrap(), - type_: field_type, - }] - .try_into() - .unwrap(), - }) - } - - fn enum_entry(name: &str) -> ScSpecEntry { - ScSpecEntry::UdtEnumV0(ScSpecUdtEnumV0 { - doc: StringM::default(), - lib: StringM::default(), - name: name.try_into().unwrap(), - cases: VecM::default(), - }) - } - - fn fn_entry(name: &str, input_type: ScSpecTypeDef) -> ScSpecEntry { - ScSpecEntry::FunctionV0(ScSpecFunctionV0 { - doc: StringM::default(), - name: name.try_into().unwrap(), - inputs: vec![ScSpecFunctionInputV0 { - doc: StringM::default(), - name: "arg".try_into().unwrap(), - type_: input_type, - }] - .try_into() - .unwrap(), - outputs: VecM::default(), - }) - } - - fn entry_name(entry: &ScSpecEntry) -> String { - match entry { - ScSpecEntry::UdtStructV0(s) => s.name.to_utf8_string_lossy(), - ScSpecEntry::UdtUnionV0(u) => u.name.to_utf8_string_lossy(), - ScSpecEntry::UdtEnumV0(e) => e.name.to_utf8_string_lossy(), - ScSpecEntry::UdtErrorEnumV0(e) => e.name.to_utf8_string_lossy(), - _ => String::new(), - } - } - - #[test] - fn no_qualified_names_is_a_noop() { - let spec = vec![enum_entry("State"), struct_entry("Point", udt("State"))]; - let (reduced, report) = reduce_udt_names(&spec); - assert!(report.is_empty()); - assert_eq!(reduced, spec); - } - - #[test] - fn shortens_and_rewrites_references() { - let spec = vec![ - enum_entry("my_contract::inner::State"), - fn_entry("run", udt("my_contract::inner::State")), - ]; - let (reduced, report) = reduce_udt_names(&spec); - - assert_eq!(entry_name(&reduced[0]), "State"); - // The reference inside the function input is rewritten too. - let ScSpecEntry::FunctionV0(f) = &reduced[1] else { - panic!("expected function") - }; - assert_eq!(f.inputs[0].type_, udt("State")); - - assert_eq!(report.renames.len(), 1); - assert_eq!(report.renames[0].from, "my_contract::inner::State"); - assert_eq!(report.renames[0].to, "State"); - assert!(report.collisions.is_empty()); - } - - #[test] - fn disambiguates_collisions_with_suffixes() { - let spec = vec![ - enum_entry("my_contract::a::Status"), - enum_entry("my_contract::b::Status"), - fn_entry("run", udt("my_contract::b::Status")), - ]; - let (reduced, report) = reduce_udt_names(&spec); - - // Deterministic: `a::Status` sorts first and keeps the bare name. - assert_eq!(entry_name(&reduced[0]), "Status"); - assert_eq!(entry_name(&reduced[1]), "Status1"); - // The reference to `b::Status` follows its rename to `Status1`. - let ScSpecEntry::FunctionV0(f) = &reduced[2] else { - panic!("expected function") - }; - assert_eq!(f.inputs[0].type_, udt("Status1")); - - assert_eq!(report.collisions.len(), 1); - let collision = &report.collisions[0]; - assert_eq!(collision.short, "Status"); - assert_eq!(collision.members.len(), 2); - assert_eq!(collision.members[0].to, "Status"); - assert_eq!(collision.members[1].to, "Status1"); - } -} diff --git a/cmd/soroban-cli/src/commands/contract/info/interface.rs b/cmd/soroban-cli/src/commands/contract/info/interface.rs index 7a5f16c1b9..08c90af42b 100644 --- a/cmd/soroban-cli/src/commands/contract/info/interface.rs +++ b/cmd/soroban-cli/src/commands/contract/info/interface.rs @@ -71,27 +71,33 @@ impl Cmd { // below cannot use them as-is. Reduce them to short names for display, // reporting what changed. The `XdrBase64` output is the canonical // on-chain spec, so it is left untouched. - let (reduced_spec, reduction) = soroban_spec_tools::reduce::reduce_udt_names(&spec); + let reduced = soroban_spec::reduce::reduce(&spec); if !matches!(self.output, InfoOutput::XdrBase64) { - for rename in &reduction.renames { + for rename in reduced.renames().filter(|r| r.renamed()) { print.infoln(format!( "Reduced type name {} to {}", - rename.from, rename.to + String::from_utf8_lossy(&rename.from), + String::from_utf8_lossy(&rename.to), )); } - if !reduction.collisions.is_empty() { + let collisions: Vec<_> = reduced.renames().filter(|r| r.collision()).collect(); + if !collisions.is_empty() { use std::fmt::Write as _; let mut msg = String::from( "Reduced type names collided and were disambiguated with a numeric suffix:", ); - for collision in &reduction.collisions { - for member in &collision.members { - let _ = write!(msg, "\n {} -> {}", member.from, member.to); - } + for rename in collisions { + let _ = write!( + msg, + "\n {} -> {}", + String::from_utf8_lossy(&rename.from), + String::from_utf8_lossy(&rename.to), + ); } print.warnln(msg); } } + let reduced_spec: Vec<_> = reduced.into_entries().collect(); let res = match self.output { InfoOutput::XdrBase64 => base64, From 25d5cad2afc4706f4ff5918da67a4243a306e7ed Mon Sep 17 00:00:00 2001 From: Leigh <351529+leighmcculloch@users.noreply.github.com> Date: Fri, 4 Sep 2026 10:40:45 +0000 Subject: [PATCH 06/10] bump soroban-sdk pr 1970 patch to head 788b536a --- Cargo.lock | 16 ++++++++-------- Cargo.toml | 12 ++++++------ deny.toml | 2 +- 3 files changed, 15 insertions(+), 15 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ca7aeac1b4..aa3f39a65f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5535,7 +5535,7 @@ version = "28.0.0" [[package]] name = "soroban-ledger-snapshot" version = "28.0.0-rc.1" -source = "git+https://github.com/stellar/rs-soroban-sdk?rev=4d8faeff872aa7567de6fcf07bb33ae74500ab5a#4d8faeff872aa7567de6fcf07bb33ae74500ab5a" +source = "git+https://github.com/stellar/rs-soroban-sdk?rev=788b536af88a1b0a4d473dc8050b6a44c668e80e#788b536af88a1b0a4d473dc8050b6a44c668e80e" dependencies = [ "serde", "serde_json", @@ -5548,7 +5548,7 @@ dependencies = [ [[package]] name = "soroban-sdk" version = "28.0.0-rc.1" -source = "git+https://github.com/stellar/rs-soroban-sdk?rev=4d8faeff872aa7567de6fcf07bb33ae74500ab5a#4d8faeff872aa7567de6fcf07bb33ae74500ab5a" +source = "git+https://github.com/stellar/rs-soroban-sdk?rev=788b536af88a1b0a4d473dc8050b6a44c668e80e#788b536af88a1b0a4d473dc8050b6a44c668e80e" dependencies = [ "arbitrary", "bytes-lit", @@ -5573,7 +5573,7 @@ dependencies = [ [[package]] name = "soroban-sdk-macros" version = "28.0.0-rc.1" -source = "git+https://github.com/stellar/rs-soroban-sdk?rev=4d8faeff872aa7567de6fcf07bb33ae74500ab5a#4d8faeff872aa7567de6fcf07bb33ae74500ab5a" +source = "git+https://github.com/stellar/rs-soroban-sdk?rev=788b536af88a1b0a4d473dc8050b6a44c668e80e#788b536af88a1b0a4d473dc8050b6a44c668e80e" dependencies = [ "darling 0.20.11", "heck 0.5.0", @@ -5591,7 +5591,7 @@ dependencies = [ [[package]] name = "soroban-spec" version = "28.0.0-rc.1" -source = "git+https://github.com/stellar/rs-soroban-sdk?rev=4d8faeff872aa7567de6fcf07bb33ae74500ab5a#4d8faeff872aa7567de6fcf07bb33ae74500ab5a" +source = "git+https://github.com/stellar/rs-soroban-sdk?rev=788b536af88a1b0a4d473dc8050b6a44c668e80e#788b536af88a1b0a4d473dc8050b6a44c668e80e" dependencies = [ "base64 0.22.1", "stellar-xdr", @@ -5602,7 +5602,7 @@ dependencies = [ [[package]] name = "soroban-spec-rust" version = "28.0.0-rc.1" -source = "git+https://github.com/stellar/rs-soroban-sdk?rev=4d8faeff872aa7567de6fcf07bb33ae74500ab5a#4d8faeff872aa7567de6fcf07bb33ae74500ab5a" +source = "git+https://github.com/stellar/rs-soroban-sdk?rev=788b536af88a1b0a4d473dc8050b6a44c668e80e#788b536af88a1b0a4d473dc8050b6a44c668e80e" dependencies = [ "prettyplease", "proc-macro2", @@ -5700,7 +5700,7 @@ dependencies = [ [[package]] name = "soroban-token-sdk" version = "28.0.0-rc.1" -source = "git+https://github.com/stellar/rs-soroban-sdk?rev=4d8faeff872aa7567de6fcf07bb33ae74500ab5a#4d8faeff872aa7567de6fcf07bb33ae74500ab5a" +source = "git+https://github.com/stellar/rs-soroban-sdk?rev=788b536af88a1b0a4d473dc8050b6a44c668e80e#788b536af88a1b0a4d473dc8050b6a44c668e80e" dependencies = [ "soroban-sdk", ] @@ -5708,7 +5708,7 @@ dependencies = [ [[package]] name = "soroban-token-spec" version = "28.0.0-rc.1" -source = "git+https://github.com/stellar/rs-soroban-sdk?rev=4d8faeff872aa7567de6fcf07bb33ae74500ab5a#4d8faeff872aa7567de6fcf07bb33ae74500ab5a" +source = "git+https://github.com/stellar/rs-soroban-sdk?rev=788b536af88a1b0a4d473dc8050b6a44c668e80e#788b536af88a1b0a4d473dc8050b6a44c668e80e" dependencies = [ "soroban-sdk", "soroban-token-sdk", @@ -5758,7 +5758,7 @@ checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" [[package]] name = "stellar-asset-spec" version = "28.0.0-rc.1" -source = "git+https://github.com/stellar/rs-soroban-sdk?rev=4d8faeff872aa7567de6fcf07bb33ae74500ab5a#4d8faeff872aa7567de6fcf07bb33ae74500ab5a" +source = "git+https://github.com/stellar/rs-soroban-sdk?rev=788b536af88a1b0a4d473dc8050b6a44c668e80e#788b536af88a1b0a4d473dc8050b6a44c668e80e" dependencies = [ "soroban-sdk", "soroban-token-sdk", diff --git a/Cargo.toml b/Cargo.toml index 20f1a8d1c6..797c3bd3af 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -128,12 +128,12 @@ astral-tokio-tar = "0.6.0" stellar-xdr = { git = "https://github.com/stellar/rs-stellar-xdr", rev = "82677e1dfe49b6b94fc6440d9c58be74d2a33bff" } # Pending https://github.com/stellar/rs-soroban-sdk/pull/1970, which names # user-defined types in contract specs by their fully qualified path. -soroban-spec = { git = "https://github.com/stellar/rs-soroban-sdk", rev = "4d8faeff872aa7567de6fcf07bb33ae74500ab5a" } -soroban-spec-rust = { git = "https://github.com/stellar/rs-soroban-sdk", rev = "4d8faeff872aa7567de6fcf07bb33ae74500ab5a" } -soroban-sdk = { git = "https://github.com/stellar/rs-soroban-sdk", rev = "4d8faeff872aa7567de6fcf07bb33ae74500ab5a" } -soroban-token-sdk = { git = "https://github.com/stellar/rs-soroban-sdk", rev = "4d8faeff872aa7567de6fcf07bb33ae74500ab5a" } -stellar-asset-spec = { git = "https://github.com/stellar/rs-soroban-sdk", rev = "4d8faeff872aa7567de6fcf07bb33ae74500ab5a" } -soroban-ledger-snapshot = { git = "https://github.com/stellar/rs-soroban-sdk", rev = "4d8faeff872aa7567de6fcf07bb33ae74500ab5a" } +soroban-spec = { git = "https://github.com/stellar/rs-soroban-sdk", rev = "788b536af88a1b0a4d473dc8050b6a44c668e80e" } +soroban-spec-rust = { git = "https://github.com/stellar/rs-soroban-sdk", rev = "788b536af88a1b0a4d473dc8050b6a44c668e80e" } +soroban-sdk = { git = "https://github.com/stellar/rs-soroban-sdk", rev = "788b536af88a1b0a4d473dc8050b6a44c668e80e" } +soroban-token-sdk = { git = "https://github.com/stellar/rs-soroban-sdk", rev = "788b536af88a1b0a4d473dc8050b6a44c668e80e" } +stellar-asset-spec = { git = "https://github.com/stellar/rs-soroban-sdk", rev = "788b536af88a1b0a4d473dc8050b6a44c668e80e" } +soroban-ledger-snapshot = { git = "https://github.com/stellar/rs-soroban-sdk", rev = "788b536af88a1b0a4d473dc8050b6a44c668e80e" } [profile.release] overflow-checks = true diff --git a/deny.toml b/deny.toml index b41c100999..6ae9eaf1b2 100644 --- a/deny.toml +++ b/deny.toml @@ -239,7 +239,7 @@ allow-git = [ "https://github.com/stellar/rs-stellar-xdr?rev=82677e1dfe49b6b94fc6440d9c58be74d2a33bff", # https://github.com/stellar/rs-soroban-sdk/pull/1970, which names # user-defined types in contract specs by their fully qualified path. - "https://github.com/stellar/rs-soroban-sdk?rev=4d8faeff872aa7567de6fcf07bb33ae74500ab5a", + "https://github.com/stellar/rs-soroban-sdk?rev=788b536af88a1b0a4d473dc8050b6a44c668e80e", ] [sources.allow-org] From 1f3527ee55ebeae24cee0d936cd617f1b5620994 Mon Sep 17 00:00:00 2001 From: Leigh <351529+leighmcculloch@users.noreply.github.com> Date: Fri, 4 Sep 2026 15:29:44 +0000 Subject: [PATCH 07/10] reduce spec type names on contract build --- .../src/commands/contract/build.rs | 100 ++++++++++++++++++ 1 file changed, 100 insertions(+) diff --git a/cmd/soroban-cli/src/commands/contract/build.rs b/cmd/soroban-cli/src/commands/contract/build.rs index 5a00d1894b..682e3f3743 100644 --- a/cmd/soroban-cli/src/commands/contract/build.rs +++ b/cmd/soroban-cli/src/commands/contract/build.rs @@ -366,6 +366,7 @@ impl Cmd { self.inject_meta(&target_file_path)?; Self::filter_spec(&target_file_path)?; + Self::reduce_spec(&print, &p.name, &target_file_path)?; let final_path = if let Some(out_dir) = &self.out_dir { fs::create_dir_all(out_dir).map_err(Error::CreatingOutDir)?; @@ -535,6 +536,65 @@ impl Cmd { fs::write(target_file_path, new_wasm).map_err(Error::WritingWasmFile) } + /// Reduces user-defined type names in the contract spec to their simple + /// form, rewriting the `contractspecv0` section in place. + /// + /// Contract specs may name user-defined types by their fully qualified path + /// (e.g. `my_contract::inner::State`). This follows each to its simple name + /// (e.g. `State`), rewriting every reference, so the on-chain spec and every + /// downstream tool see the short names. Names that would collide are + /// disambiguated with a numeric suffix, which is warned about. + /// + /// Runs after `filter_spec` so only the entries that survive shaking are + /// reduced, but is otherwise independent of spec shaking. + fn reduce_spec(print: &Print, name: &str, target_file_path: &PathBuf) -> Result<(), Error> { + use soroban_spec_tools::contract::Spec; + use soroban_spec_tools::wasm::replace_custom_section; + + let wasm_bytes = fs::read(target_file_path).map_err(Error::ReadingWasmFile)?; + let spec = Spec::new(&wasm_bytes)?; + + let reduced = soroban_spec::reduce::reduce(&spec.spec); + + // If every name was already simple, leave the wasm untouched. + if reduced.renames().all(|r| !r.renamed()) { + return Ok(()); + } + + let collisions: Vec<_> = reduced.renames().filter(|r| r.collision()).collect(); + if !collisions.is_empty() { + use std::fmt::Write as _; + let mut msg = format!( + "{name}: reduced type names collided and were disambiguated with a numeric suffix:" + ); + for rename in collisions { + let _ = write!( + msg, + "\n {} -> {}", + String::from_utf8_lossy(&rename.from), + String::from_utf8_lossy(&rename.to), + ); + } + print.warnln(msg); + } + + // Encode the reduced entries and replace the contractspecv0 section. + let mut reduced_xdr = Vec::new(); + let mut writer = Limited::new( + Cursor::new(&mut reduced_xdr), + Limits::depth(XDR_DEPTH_LIMIT), + ); + for entry in reduced.into_entries() { + entry.write_xdr(&mut writer)?; + } + + let new_wasm = replace_custom_section(&wasm_bytes, "contractspecv0", &reduced_xdr) + .map_err(|e| Error::WasmParsing(e.to_string()))?; + + fs::remove_file(target_file_path).map_err(Error::DeletingArtifact)?; + fs::write(target_file_path, new_wasm).map_err(Error::WritingWasmFile) + } + fn encoded_new_meta(&self) -> Result, Error> { let mut new_meta: Vec = Vec::new(); @@ -936,6 +996,46 @@ mod tests { assert!(Cmd::try_parse_from(["build", "--pull"]).is_err()); } + #[test] + fn reduce_spec_shortens_qualified_names_in_wasm() { + use soroban_spec_tools::contract::Spec; + use soroban_spec_tools::wasm::replace_custom_section; + use stellar_xdr::{ScSpecEntry, ScSpecUdtStructV0, VecM}; + + // A spec with a single struct whose name is fully qualified. + let entry = ScSpecEntry::UdtStructV0(ScSpecUdtStructV0 { + doc: StringM::default(), + lib: StringM::default(), + name: "mycrate::mymod::MyType".to_string().try_into().unwrap(), + fields: VecM::default(), + }); + let mut spec_xdr = Vec::new(); + entry + .write_xdr(&mut Limited::new( + Cursor::new(&mut spec_xdr), + Limits::depth(XDR_DEPTH_LIMIT), + )) + .unwrap(); + + // Embed the spec in a minimal (empty) wasm module and write it out. + let wasm = replace_custom_section(b"\0asm\x01\0\0\0", "contractspecv0", &spec_xdr).unwrap(); + let path = + env::temp_dir().join(format!("reduce_spec_test_{}.wasm", std::process::id())); + fs::write(&path, &wasm).unwrap(); + + Cmd::reduce_spec(&Print::new(true), "pkg", &path).unwrap(); + + let out = fs::read(&path).unwrap(); + fs::remove_file(&path).ok(); + let spec = Spec::new(&out).unwrap(); + + assert_eq!(spec.spec.len(), 1); + let ScSpecEntry::UdtStructV0(s) = &spec.spec[0] else { + panic!("expected a struct entry, got {:?}", spec.spec[0]); + }; + assert_eq!(s.name.to_vec(), b"MyType".to_vec()); + } + #[test] fn serialize_command_shell_escapes_args_with_metacharacters() { let raw_arg = "--manifest-path=/path/to/contract;touch PWNED;#/Cargo.toml"; From 432d3a54d620cd29805ba116834ff9160e9c02b3 Mon Sep 17 00:00:00 2001 From: Leigh <351529+leighmcculloch@users.noreply.github.com> Date: Tue, 8 Sep 2026 07:25:32 +0000 Subject: [PATCH 08/10] inform sdk that cli reduces qualified names --- cmd/crates/soroban-test/tests/it/build.rs | 10 +++++++--- cmd/soroban-cli/src/commands/contract/build.rs | 5 +++++ 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/cmd/crates/soroban-test/tests/it/build.rs b/cmd/crates/soroban-test/tests/it/build.rs index 84c42fd32c..0e1605269e 100644 --- a/cmd/crates/soroban-test/tests/it/build.rs +++ b/cmd/crates/soroban-test/tests/it/build.rs @@ -736,7 +736,11 @@ fn parent_path() -> String { } fn with_flags(expected: &str) -> String { - const ENV_VAR: &str = "SOROBAN_SDK_BUILD_SYSTEM_SUPPORTS_SPEC_SHAKING_V2=1"; + // Serialized in sorted key order, so REDUCING_FULL_NAMES precedes SPEC_SHAKING_V2. + const ENV_VARS: &str = concat!( + "SOROBAN_SDK_BUILD_SYSTEM_SUPPORTS_REDUCING_FULL_NAMES=1 ", + "SOROBAN_SDK_BUILD_SYSTEM_SUPPORTS_SPEC_SHAKING_V2=1" + ); let cargo_home = home::cargo_home().unwrap(); let registry_prefix = cargo_home.join("registry").join("src"); @@ -747,7 +751,7 @@ fn with_flags(expected: &str) -> String { let vec: Vec<_> = if env::var("RUSTFLAGS").is_ok() { expected .split('\n') - .map(|x| format!("{ENV_VAR} {x}")) + .map(|x| format!("{ENV_VARS} {x}")) .collect() } else { expected @@ -755,7 +759,7 @@ fn with_flags(expected: &str) -> String { .map(|x| { let rustflags_value = format!("--remap-path-prefix={registry_prefix}="); let escaped_value = escape(std::borrow::Cow::Borrowed(&rustflags_value)); - format!("CARGO_BUILD_RUSTFLAGS={escaped_value} {ENV_VAR} {x}") + format!("CARGO_BUILD_RUSTFLAGS={escaped_value} {ENV_VARS} {x}") }) .collect() }; diff --git a/cmd/soroban-cli/src/commands/contract/build.rs b/cmd/soroban-cli/src/commands/contract/build.rs index 682e3f3743..ab0e5f57af 100644 --- a/cmd/soroban-cli/src/commands/contract/build.rs +++ b/cmd/soroban-cli/src/commands/contract/build.rs @@ -346,6 +346,11 @@ impl Cmd { // optimization using markers. cmd.env("SOROBAN_SDK_BUILD_SYSTEM_SUPPORTS_SPEC_SHAKING_V2", "1"); + // Set env var to inform the SDK that this CLI reduces fully + // qualified user-defined type names in the spec down to their + // simple names. + cmd.env("SOROBAN_SDK_BUILD_SYSTEM_SUPPORTS_REDUCING_FULL_NAMES", "1"); + let cmd_str = serialize_command(&cmd); if self.print_commands_only { From 40b9e16e6a947485f114778a3de6581b69bd52ee Mon Sep 17 00:00:00 2001 From: Leigh <351529+leighmcculloch@users.noreply.github.com> Date: Tue, 8 Sep 2026 13:44:32 +0000 Subject: [PATCH 09/10] update patches to the rebased sdk and xdr --- Cargo.lock | 54 +++++++++++++++++++++++------------------------------- Cargo.toml | 20 +++++++++++++------- Makefile | 2 +- 3 files changed, 37 insertions(+), 39 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index aa3f39a65f..b566a0e2e8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5344,9 +5344,8 @@ dependencies = [ [[package]] name = "soroban-builtin-sdk-macros" -version = "28.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86ffd329211a50add3965a8f4a8f70e24fc5c8268c945566ab963029b12dce40" +version = "29.0.0" +source = "git+https://github.com/stellar/rs-soroban-env?rev=de68ee914401e70d3d8350a4d004c0442943324e#de68ee914401e70d3d8350a4d004c0442943324e" dependencies = [ "itertools 0.13.0", "proc-macro2", @@ -5449,9 +5448,8 @@ dependencies = [ [[package]] name = "soroban-env-common" -version = "28.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "610bb12ee937a56c2b629f8eb16efb7e47607cba4e79ab0c24132b9a53dd19a0" +version = "29.0.0" +source = "git+https://github.com/stellar/rs-soroban-env?rev=de68ee914401e70d3d8350a4d004c0442943324e#de68ee914401e70d3d8350a4d004c0442943324e" dependencies = [ "arbitrary", "crate-git-revision 0.0.9", @@ -5468,9 +5466,8 @@ dependencies = [ [[package]] name = "soroban-env-guest" -version = "28.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d383176b27bb286f718ab0ff5713bd9b01132089151266359bd9b20f38684cf" +version = "29.0.0" +source = "git+https://github.com/stellar/rs-soroban-env?rev=de68ee914401e70d3d8350a4d004c0442943324e#de68ee914401e70d3d8350a4d004c0442943324e" dependencies = [ "soroban-env-common", "static_assertions", @@ -5478,9 +5475,8 @@ dependencies = [ [[package]] name = "soroban-env-host" -version = "28.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36d7e5920c200939164cc58038224e2d90d7e2fabbfa505d6e2129d648dce7e2" +version = "29.0.0" +source = "git+https://github.com/stellar/rs-soroban-env?rev=de68ee914401e70d3d8350a4d004c0442943324e#de68ee914401e70d3d8350a4d004c0442943324e" dependencies = [ "ark-bls12-381", "ark-bn254", @@ -5515,9 +5511,8 @@ dependencies = [ [[package]] name = "soroban-env-macros" -version = "28.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "077600bb175ea54592c1ac5983b8fb34ec9ab7a65f298af11d687b61e9d646a4" +version = "29.0.0" +source = "git+https://github.com/stellar/rs-soroban-env?rev=de68ee914401e70d3d8350a4d004c0442943324e#de68ee914401e70d3d8350a4d004c0442943324e" dependencies = [ "itertools 0.13.0", "proc-macro2", @@ -5535,7 +5530,7 @@ version = "28.0.0" [[package]] name = "soroban-ledger-snapshot" version = "28.0.0-rc.1" -source = "git+https://github.com/stellar/rs-soroban-sdk?rev=788b536af88a1b0a4d473dc8050b6a44c668e80e#788b536af88a1b0a4d473dc8050b6a44c668e80e" +source = "git+https://github.com/stellar/rs-soroban-sdk?rev=7093a641145329526b221e683fbb5e74f9710053#7093a641145329526b221e683fbb5e74f9710053" dependencies = [ "serde", "serde_json", @@ -5548,7 +5543,7 @@ dependencies = [ [[package]] name = "soroban-sdk" version = "28.0.0-rc.1" -source = "git+https://github.com/stellar/rs-soroban-sdk?rev=788b536af88a1b0a4d473dc8050b6a44c668e80e#788b536af88a1b0a4d473dc8050b6a44c668e80e" +source = "git+https://github.com/stellar/rs-soroban-sdk?rev=7093a641145329526b221e683fbb5e74f9710053#7093a641145329526b221e683fbb5e74f9710053" dependencies = [ "arbitrary", "bytes-lit", @@ -5573,7 +5568,7 @@ dependencies = [ [[package]] name = "soroban-sdk-macros" version = "28.0.0-rc.1" -source = "git+https://github.com/stellar/rs-soroban-sdk?rev=788b536af88a1b0a4d473dc8050b6a44c668e80e#788b536af88a1b0a4d473dc8050b6a44c668e80e" +source = "git+https://github.com/stellar/rs-soroban-sdk?rev=7093a641145329526b221e683fbb5e74f9710053#7093a641145329526b221e683fbb5e74f9710053" dependencies = [ "darling 0.20.11", "heck 0.5.0", @@ -5591,7 +5586,7 @@ dependencies = [ [[package]] name = "soroban-spec" version = "28.0.0-rc.1" -source = "git+https://github.com/stellar/rs-soroban-sdk?rev=788b536af88a1b0a4d473dc8050b6a44c668e80e#788b536af88a1b0a4d473dc8050b6a44c668e80e" +source = "git+https://github.com/stellar/rs-soroban-sdk?rev=7093a641145329526b221e683fbb5e74f9710053#7093a641145329526b221e683fbb5e74f9710053" dependencies = [ "base64 0.22.1", "stellar-xdr", @@ -5602,7 +5597,7 @@ dependencies = [ [[package]] name = "soroban-spec-rust" version = "28.0.0-rc.1" -source = "git+https://github.com/stellar/rs-soroban-sdk?rev=788b536af88a1b0a4d473dc8050b6a44c668e80e#788b536af88a1b0a4d473dc8050b6a44c668e80e" +source = "git+https://github.com/stellar/rs-soroban-sdk?rev=7093a641145329526b221e683fbb5e74f9710053#7093a641145329526b221e683fbb5e74f9710053" dependencies = [ "prettyplease", "proc-macro2", @@ -5700,7 +5695,7 @@ dependencies = [ [[package]] name = "soroban-token-sdk" version = "28.0.0-rc.1" -source = "git+https://github.com/stellar/rs-soroban-sdk?rev=788b536af88a1b0a4d473dc8050b6a44c668e80e#788b536af88a1b0a4d473dc8050b6a44c668e80e" +source = "git+https://github.com/stellar/rs-soroban-sdk?rev=7093a641145329526b221e683fbb5e74f9710053#7093a641145329526b221e683fbb5e74f9710053" dependencies = [ "soroban-sdk", ] @@ -5708,7 +5703,7 @@ dependencies = [ [[package]] name = "soroban-token-spec" version = "28.0.0-rc.1" -source = "git+https://github.com/stellar/rs-soroban-sdk?rev=788b536af88a1b0a4d473dc8050b6a44c668e80e#788b536af88a1b0a4d473dc8050b6a44c668e80e" +source = "git+https://github.com/stellar/rs-soroban-sdk?rev=7093a641145329526b221e683fbb5e74f9710053#7093a641145329526b221e683fbb5e74f9710053" dependencies = [ "soroban-sdk", "soroban-token-sdk", @@ -5717,8 +5712,7 @@ dependencies = [ [[package]] name = "soroban-wasmi" version = "0.31.1-soroban.20.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "710403de32d0e0c35375518cb995d4fc056d0d48966f2e56ea471b8cb8fc9719" +source = "git+https://github.com/stellar/wasmi?rev=0ed3f3dee30dc41ebe21972399e0a73a41944aa0#0ed3f3dee30dc41ebe21972399e0a73a41944aa0" dependencies = [ "smallvec", "spin", @@ -5758,7 +5752,7 @@ checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" [[package]] name = "stellar-asset-spec" version = "28.0.0-rc.1" -source = "git+https://github.com/stellar/rs-soroban-sdk?rev=788b536af88a1b0a4d473dc8050b6a44c668e80e#788b536af88a1b0a4d473dc8050b6a44c668e80e" +source = "git+https://github.com/stellar/rs-soroban-sdk?rev=7093a641145329526b221e683fbb5e74f9710053#7093a641145329526b221e683fbb5e74f9710053" dependencies = [ "soroban-sdk", "soroban-token-sdk", @@ -5874,7 +5868,7 @@ dependencies = [ [[package]] name = "stellar-xdr" version = "28.0.0" -source = "git+https://github.com/stellar/rs-stellar-xdr?rev=82677e1dfe49b6b94fc6440d9c58be74d2a33bff#82677e1dfe49b6b94fc6440d9c58be74d2a33bff" +source = "git+https://github.com/stellar/rs-stellar-xdr?rev=9c6c2f8c09b678d1ebed87b3f41f621068badcf4#9c6c2f8c09b678d1ebed87b3f41f621068badcf4" dependencies = [ "arbitrary", "base64 0.22.1", @@ -7082,15 +7076,13 @@ dependencies = [ [[package]] name = "wasmi_arena" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "104a7f73be44570cac297b3035d76b169d6599637631cf37a1703326a0727073" +version = "0.4.0" +source = "git+https://github.com/stellar/wasmi?rev=0ed3f3dee30dc41ebe21972399e0a73a41944aa0#0ed3f3dee30dc41ebe21972399e0a73a41944aa0" [[package]] name = "wasmi_core" version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcf1a7db34bff95b85c261002720c00c3a6168256dcb93041d3fa2054d19856a" +source = "git+https://github.com/stellar/wasmi?rev=0ed3f3dee30dc41ebe21972399e0a73a41944aa0#0ed3f3dee30dc41ebe21972399e0a73a41944aa0" dependencies = [ "downcast-rs", "libm", diff --git a/Cargo.toml b/Cargo.toml index 797c3bd3af..76b6384b0a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -125,15 +125,21 @@ astral-tokio-tar = "0.6.0" # Pending https://github.com/stellar/rs-stellar-xdr/pull/566, which widens the # user-defined type name limit so that a fully qualified type name fits. Pinned # to the same rev rs-soroban-sdk#1970 uses so the graph has one stellar-xdr. -stellar-xdr = { git = "https://github.com/stellar/rs-stellar-xdr", rev = "82677e1dfe49b6b94fc6440d9c58be74d2a33bff" } +stellar-xdr = { git = "https://github.com/stellar/rs-stellar-xdr", rev = "9c6c2f8c09b678d1ebed87b3f41f621068badcf4" } # Pending https://github.com/stellar/rs-soroban-sdk/pull/1970, which names # user-defined types in contract specs by their fully qualified path. -soroban-spec = { git = "https://github.com/stellar/rs-soroban-sdk", rev = "788b536af88a1b0a4d473dc8050b6a44c668e80e" } -soroban-spec-rust = { git = "https://github.com/stellar/rs-soroban-sdk", rev = "788b536af88a1b0a4d473dc8050b6a44c668e80e" } -soroban-sdk = { git = "https://github.com/stellar/rs-soroban-sdk", rev = "788b536af88a1b0a4d473dc8050b6a44c668e80e" } -soroban-token-sdk = { git = "https://github.com/stellar/rs-soroban-sdk", rev = "788b536af88a1b0a4d473dc8050b6a44c668e80e" } -stellar-asset-spec = { git = "https://github.com/stellar/rs-soroban-sdk", rev = "788b536af88a1b0a4d473dc8050b6a44c668e80e" } -soroban-ledger-snapshot = { git = "https://github.com/stellar/rs-soroban-sdk", rev = "788b536af88a1b0a4d473dc8050b6a44c668e80e" } +soroban-spec = { git = "https://github.com/stellar/rs-soroban-sdk", rev = "7093a641145329526b221e683fbb5e74f9710053" } +soroban-spec-rust = { git = "https://github.com/stellar/rs-soroban-sdk", rev = "7093a641145329526b221e683fbb5e74f9710053" } +soroban-sdk = { git = "https://github.com/stellar/rs-soroban-sdk", rev = "7093a641145329526b221e683fbb5e74f9710053" } +soroban-token-sdk = { git = "https://github.com/stellar/rs-soroban-sdk", rev = "7093a641145329526b221e683fbb5e74f9710053" } +stellar-asset-spec = { git = "https://github.com/stellar/rs-soroban-sdk", rev = "7093a641145329526b221e683fbb5e74f9710053" } +soroban-ledger-snapshot = { git = "https://github.com/stellar/rs-soroban-sdk", rev = "7093a641145329526b221e683fbb5e74f9710053" } +# Pending https://github.com/stellar/rs-soroban-env/pull/1733, which adapts the +# host to the XDR constants that rs-stellar-xdr#580 changes from u64 to u32. +# rs-soroban-sdk#1970 is on that 29.x line, so pin to the same rev it uses. +soroban-env-common = { git = "https://github.com/stellar/rs-soroban-env", rev = "de68ee914401e70d3d8350a4d004c0442943324e" } +soroban-env-guest = { git = "https://github.com/stellar/rs-soroban-env", rev = "de68ee914401e70d3d8350a4d004c0442943324e" } +soroban-env-host = { git = "https://github.com/stellar/rs-soroban-env", rev = "de68ee914401e70d3d8350a4d004c0442943324e" } [profile.release] overflow-checks = true diff --git a/Makefile b/Makefile index 397040e81d..9f1ddbd4ea 100644 --- a/Makefile +++ b/Makefile @@ -39,7 +39,7 @@ build: cargo build build-test-wasms: - SOROBAN_SDK_BUILD_SYSTEM_SUPPORTS_SPEC_SHAKING_V2=1 cargo build --package 'test_*' --profile test-wasms --target wasm32v1-none + SOROBAN_SDK_BUILD_SYSTEM_SUPPORTS_SPEC_SHAKING_V2=1 SOROBAN_SDK_BUILD_SYSTEM_SUPPORTS_REDUCING_FULL_NAMES=1 cargo build --package 'test_*' --profile test-wasms --target wasm32v1-none build-test: build-test-wasms build-fixtures install From 8710d3fed70ec68f3a809e145b68ffe71c440423 Mon Sep 17 00:00:00 2001 From: Leigh <351529+leighmcculloch@users.noreply.github.com> Date: Tue, 8 Sep 2026 21:43:53 +0000 Subject: [PATCH 10/10] show raw spec type names in contract info --- .../src/commands/contract/info/interface.rs | 42 +++---------------- 1 file changed, 5 insertions(+), 37 deletions(-) diff --git a/cmd/soroban-cli/src/commands/contract/info/interface.rs b/cmd/soroban-cli/src/commands/contract/info/interface.rs index 08c90af42b..2ab2aaf1c7 100644 --- a/cmd/soroban-cli/src/commands/contract/info/interface.rs +++ b/cmd/soroban-cli/src/commands/contract/info/interface.rs @@ -65,51 +65,19 @@ impl Cmd { } }; - // Contract specs may name user-defined types by their fully qualified - // path (e.g. `my_contract::inner::State`). Those names are noisy and, - // because `::` is not a valid identifier, the Rust and JSON renderers - // below cannot use them as-is. Reduce them to short names for display, - // reporting what changed. The `XdrBase64` output is the canonical - // on-chain spec, so it is left untouched. - let reduced = soroban_spec::reduce::reduce(&spec); - if !matches!(self.output, InfoOutput::XdrBase64) { - for rename in reduced.renames().filter(|r| r.renamed()) { - print.infoln(format!( - "Reduced type name {} to {}", - String::from_utf8_lossy(&rename.from), - String::from_utf8_lossy(&rename.to), - )); - } - let collisions: Vec<_> = reduced.renames().filter(|r| r.collision()).collect(); - if !collisions.is_empty() { - use std::fmt::Write as _; - let mut msg = String::from( - "Reduced type names collided and were disambiguated with a numeric suffix:", - ); - for rename in collisions { - let _ = write!( - msg, - "\n {} -> {}", - String::from_utf8_lossy(&rename.from), - String::from_utf8_lossy(&rename.to), - ); - } - print.warnln(msg); - } - } - let reduced_spec: Vec<_> = reduced.into_entries().collect(); - + // Type names in the spec are already reduced to simple names during + // `contract build`, so show the spec as it is stored in the contract. let res = match self.output { InfoOutput::XdrBase64 => base64, - InfoOutput::Json => serde_json::to_string(&reduced_spec)?, - InfoOutput::JsonFormatted => serde_json::to_string_pretty(&reduced_spec)?, + InfoOutput::Json => serde_json::to_string(&spec)?, + InfoOutput::JsonFormatted => serde_json::to_string_pretty(&spec)?, // soroban_spec_rust drops doc strings entirely (rustdocs can execute // code) and routes every spec name through `format_ident!`, which // rejects non-identifier bytes. If a future revision starts // emitting spec strings as `Literal::string` or rustdocs, this // path becomes a terminal-escape-injection vector and must be // sanitized before printing. - InfoOutput::Rust => soroban_spec_rust::generate_without_file(&reduced_spec)? + InfoOutput::Rust => soroban_spec_rust::generate_without_file(&spec)? .to_formatted_string() .expect("Unexpected spec format error"), };