From 467ace19df407f2951043bb548109c12f829e5c1 Mon Sep 17 00:00:00 2001 From: Leigh <351529+leighmcculloch@users.noreply.github.com> Date: Mon, 10 Aug 2026 02:24:23 +0000 Subject: [PATCH 1/5] patch xdr and spec crates for udt type ids --- Cargo.lock | 85 ++++----- Cargo.toml | 33 +++- Makefile | 2 +- cmd/crates/soroban-spec-tools/src/lib.rs | 174 +++++++++++++----- cmd/crates/soroban-spec-tools/src/verify.rs | 1 + .../soroban-spec-typescript/src/types.rs | 3 + .../fixtures/test-wasms/token/src/contract.rs | 2 +- cmd/crates/soroban-test/tests/it/build.rs | 1 + cmd/crates/soroban-test/tests/it/log.rs | 2 +- .../src/commands/contract/arg_parsing.rs | 48 ++++- cmd/soroban-cli/src/get_spec.rs | 6 + cmd/soroban-cli/src/log/auth.rs | 9 + cmd/soroban-cli/src/wasm.rs | 9 +- deny.toml | 10 +- 14 files changed, 275 insertions(+), 110 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a63546cca6..79a035389f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5088,6 +5088,16 @@ dependencies = [ "syn 2.0.118", ] +[[package]] +name = "serde_ignored" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "115dffd5f3853e06e746965a20dcbae6ee747ae30b543d91b0e089668bb07798" +dependencies = [ + "serde", + "serde_core", +] + [[package]] name = "serde_json" version = "1.0.150" @@ -5371,9 +5381,9 @@ dependencies = [ [[package]] name = "soroban-builtin-sdk-macros" -version = "27.0.1" +version = "28.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b77bc93d930032c487cb1506b6ed166b2af49db76d52678ec4887ac621ecce01" +checksum = "dc470cc875cbdeb5ffb0fcb45017803c94b65075d493f097304b132ab95f353c" dependencies = [ "itertools 0.13.0", "proc-macro2", @@ -5474,12 +5484,12 @@ dependencies = [ [[package]] name = "soroban-env-common" -version = "27.0.1" +version = "28.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b22e9981cdd444f3aa6734bc58d76195bf7eca3ccf1dd432b875af5d02da068" +checksum = "74059594274a1b51d0ec410ef1acd9009a888e73c5f8bdd5577d28ea8766e6f2" dependencies = [ "arbitrary", - "crate-git-revision 0.0.6", + "crate-git-revision 0.0.9", "ethnum", "num-derive", "num-traits", @@ -5493,9 +5503,9 @@ dependencies = [ [[package]] name = "soroban-env-guest" -version = "27.0.1" +version = "28.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b6072f99ca6bf8e8d5b04e05d083dac785e5357d9c0f36a6658f819c2fd7d67" +checksum = "769c70c3273c4cb741046db161e804b91b8df81f9df2013dfb98d65370a716cc" dependencies = [ "soroban-env-common", "static_assertions", @@ -5503,9 +5513,9 @@ dependencies = [ [[package]] name = "soroban-env-host" -version = "27.0.1" +version = "28.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c06afd7c75ce150ce53e4d77a77645b18e3fb61856a0ddc42bfcecdc39fa3b9" +checksum = "7d3d108fff17fd4181343e29fd8d81d5053dfc29b337643e3b6c38e750eeb2f7" dependencies = [ "ark-bls12-381", "ark-bn254", @@ -5540,9 +5550,9 @@ dependencies = [ [[package]] name = "soroban-env-macros" -version = "27.0.1" +version = "28.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "647811bdd28a3ec40296987f6635781e5e1141c8f5affbbd53ba12b6295b7bb6" +checksum = "45f63006270b7ddfa604f4a12a43d1f489aa96c280877f24e37a139bb69964af" dependencies = [ "itertools 0.13.0", "proc-macro2", @@ -5559,9 +5569,8 @@ version = "27.1.0" [[package]] name = "soroban-ledger-snapshot" -version = "27.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27ea48b5d5e22c0cbaa370f813111016c9f5a8a6632edb0684c72be6531c4d3a" +version = "27.0.5" +source = "git+https://github.com/stellar/rs-soroban-sdk?rev=647dc7f29bc6151e21a2f1791e7954d260180161#647dc7f29bc6151e21a2f1791e7954d260180161" dependencies = [ "serde", "serde_json", @@ -5573,9 +5582,8 @@ dependencies = [ [[package]] name = "soroban-sdk" -version = "27.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6cfa78af0110f0830d3af9db0361b6cdb50507846cedb8725189318134218b80" +version = "27.0.5" +source = "git+https://github.com/stellar/rs-soroban-sdk?rev=647dc7f29bc6151e21a2f1791e7954d260180161#647dc7f29bc6151e21a2f1791e7954d260180161" dependencies = [ "arbitrary", "bytes-lit", @@ -5592,14 +5600,14 @@ dependencies = [ "soroban-ledger-snapshot", "soroban-sdk-macros", "stellar-strkey 0.0.16", + "stellar-xdr", "visibility", ] [[package]] name = "soroban-sdk-macros" -version = "27.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3db611bd0acfe5fd348ffdcef16c84090c6ab051f3e325b5ac049a73044a7ee6" +version = "27.0.5" +source = "git+https://github.com/stellar/rs-soroban-sdk?rev=647dc7f29bc6151e21a2f1791e7954d260180161#647dc7f29bc6151e21a2f1791e7954d260180161" dependencies = [ "darling 0.20.11", "heck 0.5.0", @@ -5609,7 +5617,6 @@ dependencies = [ "quote", "sha2 0.10.9", "soroban-env-common", - "soroban-spec", "soroban-spec-rust", "stellar-xdr", "syn 2.0.118", @@ -5617,9 +5624,8 @@ dependencies = [ [[package]] name = "soroban-spec" -version = "27.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8061fe31998fe9fba8fcd7175b3e3ca914a6f6e3bf829fe91fdf7f930b1251d9" +version = "27.0.5" +source = "git+https://github.com/stellar/rs-soroban-sdk?rev=647dc7f29bc6151e21a2f1791e7954d260180161#647dc7f29bc6151e21a2f1791e7954d260180161" dependencies = [ "base64 0.22.1", "sha2 0.10.9", @@ -5630,9 +5636,8 @@ dependencies = [ [[package]] name = "soroban-spec-rust" -version = "27.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2180cddacf3023a439827cb35c0e391ec413b1328173fe7c8c2743fc67b3af9e" +version = "27.0.5" +source = "git+https://github.com/stellar/rs-soroban-sdk?rev=647dc7f29bc6151e21a2f1791e7954d260180161#647dc7f29bc6151e21a2f1791e7954d260180161" dependencies = [ "prettyplease", "proc-macro2", @@ -5729,18 +5734,16 @@ dependencies = [ [[package]] name = "soroban-token-sdk" -version = "27.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc52c6f247f1c3383b9fa8f8ab6bf3c6c7b36e893a2fff585bc819cbc89ec193" +version = "27.0.5" +source = "git+https://github.com/stellar/rs-soroban-sdk?rev=647dc7f29bc6151e21a2f1791e7954d260180161#647dc7f29bc6151e21a2f1791e7954d260180161" dependencies = [ "soroban-sdk", ] [[package]] name = "soroban-token-spec" -version = "27.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7417a2ae69b4a6fc52f986c3d501b3bd442ba976a0991f042f81f7ee593e1b30" +version = "27.0.5" +source = "git+https://github.com/stellar/rs-soroban-sdk?rev=647dc7f29bc6151e21a2f1791e7954d260180161#647dc7f29bc6151e21a2f1791e7954d260180161" dependencies = [ "soroban-sdk", "soroban-token-sdk", @@ -5789,9 +5792,8 @@ checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" [[package]] name = "stellar-asset-spec" -version = "27.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9943559865bce30221f21f2bdd45352c91c253ddf1002dba413a7a0ffc23945" +version = "27.0.5" +source = "git+https://github.com/stellar/rs-soroban-sdk?rev=647dc7f29bc6151e21a2f1791e7954d260180161#647dc7f29bc6151e21a2f1791e7954d260180161" dependencies = [ "soroban-sdk", "soroban-token-sdk", @@ -5844,8 +5846,7 @@ dependencies = [ [[package]] name = "stellar-rpc-client" version = "27.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54b65ae2e8788c09fba690afa3b589094fd67ff4d7d5ec4502ec4f2d7d870576" +source = "git+https://github.com/stellar/rs-stellar-rpc-client?rev=e0d44a0b97959820e855eb9058e2114ac390c1fa#e0d44a0b97959820e855eb9058e2114ac390c1fa" dependencies = [ "clap", "hex", @@ -5894,21 +5895,21 @@ dependencies = [ [[package]] name = "stellar-xdr" -version = "27.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05ff843326969bdf1ef673dcdba94c08f4a3c8f1e58d6e6ef39b1bd4f749179a" +version = "28.0.0" +source = "git+https://github.com/stellar/rs-stellar-xdr?rev=182d524aa0cd34cd1b5d21ab71fbbebbd932939b#182d524aa0cd34cd1b5d21ab71fbbebbd932939b" dependencies = [ "arbitrary", "base64 0.22.1", "cfg_eval", "clap", - "crate-git-revision 0.0.6", + "crate-git-revision 0.0.9", "escape-bytes", "ethnum", "hex", "rand 0.9.2", "schemars 0.8.22", "serde", + "serde_ignored", "serde_json", "serde_with", "sha2 0.10.9", diff --git a/Cargo.toml b/Cargo.toml index 71f7573e4d..eac75c9869 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -44,30 +44,30 @@ path = "cmd/crates/stellar-ledger" # Dependencies from the rs-stellar-xdr repo: [workspace.dependencies.stellar-xdr] -version = "27.0.0" +version = "28.0.0" # Dependencies from the rs-stellar-env repo: [workspace.dependencies.soroban-env-host] -version = "27.0.1" +version = "28.0.1" # Dependencies from the rs-soroban-sdk repo: [workspace.dependencies.soroban-spec] -version = "27.0.2" +version = "27.0.5" [workspace.dependencies.soroban-spec-rust] -version = "27.0.2" +version = "27.0.5" [workspace.dependencies.soroban-sdk] -version = "27.0.2" +version = "27.0.5" [workspace.dependencies.soroban-token-sdk] -version = "27.0.2" +version = "27.0.5" [workspace.dependencies.stellar-asset-spec] -version = "27.0.2" +version = "27.0.5" [workspace.dependencies.soroban-ledger-snapshot] -version = "27.0.2" +version = "27.0.5" # Dependencies from the rs-stellar-rpc-client repo: [workspace.dependencies.soroban-rpc] @@ -120,6 +120,23 @@ 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/568, which adds +# user-defined type ids and the `ScSpecTypeUdtv2` reference type to the +# contract spec. +stellar-xdr = { git = "https://github.com/stellar/rs-stellar-xdr", rev = "182d524aa0cd34cd1b5d21ab71fbbebbd932939b" } +# Pending the rs-soroban-sdk change that stamps every user-defined type's spec +# entry with an id and marries references up with entries by id. +soroban-spec = { git = "https://github.com/stellar/rs-soroban-sdk", rev = "647dc7f29bc6151e21a2f1791e7954d260180161" } +soroban-spec-rust = { git = "https://github.com/stellar/rs-soroban-sdk", rev = "647dc7f29bc6151e21a2f1791e7954d260180161" } +soroban-sdk = { git = "https://github.com/stellar/rs-soroban-sdk", rev = "647dc7f29bc6151e21a2f1791e7954d260180161" } +soroban-token-sdk = { git = "https://github.com/stellar/rs-soroban-sdk", rev = "647dc7f29bc6151e21a2f1791e7954d260180161" } +stellar-asset-spec = { git = "https://github.com/stellar/rs-soroban-sdk", rev = "647dc7f29bc6151e21a2f1791e7954d260180161" } +soroban-ledger-snapshot = { git = "https://github.com/stellar/rs-soroban-sdk", rev = "647dc7f29bc6151e21a2f1791e7954d260180161" } +# Pending https://github.com/stellar/rs-stellar-rpc-client/pull/108, which +# updates the client to stellar-xdr 28. +stellar-rpc-client = { git = "https://github.com/stellar/rs-stellar-rpc-client", rev = "e0d44a0b97959820e855eb9058e2114ac390c1fa" } + [profile.release] overflow-checks = true diff --git a/Makefile b/Makefile index 6e633d8078..397040e81d 100644 --- a/Makefile +++ b/Makefile @@ -39,7 +39,7 @@ build: cargo build build-test-wasms: - cargo build --package 'test_*' --profile test-wasms --target wasm32v1-none + SOROBAN_SDK_BUILD_SYSTEM_SUPPORTS_SPEC_SHAKING_V2=1 cargo build --package 'test_*' --profile test-wasms --target wasm32v1-none build-test: build-test-wasms build-fixtures install diff --git a/cmd/crates/soroban-spec-tools/src/lib.rs b/cmd/crates/soroban-spec-tools/src/lib.rs index a12d5821b8..cba4c0b384 100644 --- a/cmd/crates/soroban-spec-tools/src/lib.rs +++ b/cmd/crates/soroban-spec-tools/src/lib.rs @@ -136,17 +136,9 @@ impl Spec { "Can be public key (G13..), a contract ID (C13...) or an identity (alice), ", ), ScType::Option(type_) => return self.doc(name, &type_.value_type), - ScType::Udt(ScSpecTypeUdt { name }) => { - let spec_type = self.find(&name.to_utf8_string_lossy())?; - let spec_type_match = match spec_type { - ScSpecEntry::FunctionV0(ScSpecFunctionV0 { doc, .. }) - | ScSpecEntry::UdtStructV0(ScSpecUdtStructV0 { doc, .. }) - | ScSpecEntry::UdtUnionV0(ScSpecUdtUnionV0 { doc, .. }) - | ScSpecEntry::UdtEnumV0(ScSpecUdtEnumV0 { doc, .. }) - | ScSpecEntry::UdtErrorEnumV0(ScSpecUdtErrorEnumV0 { doc, .. }) - | ScSpecEntry::EventV0(ScSpecEventV0 { doc, .. }) => doc, - }; - spec_type_match.to_utf8_string_lossy() + ScType::Udt(ScSpecTypeUdt { name }) => self.udt_doc(&name.to_utf8_string_lossy(), None)?, + ScType::UdtV2(udt) => { + self.udt_doc(&udt.name.to_utf8_string_lossy(), Some(&udt.id))? } }; @@ -170,6 +162,43 @@ impl Spec { } } + fn udt_doc(&self, name: &str, id: Option<&[u8; 8]>) -> Result { + let doc = match self.find_udt(name, id)? { + ScSpecEntry::FunctionV0(ScSpecFunctionV0 { doc, .. }) + | ScSpecEntry::UdtStructV0(ScSpecUdtStructV0 { doc, .. }) + | ScSpecEntry::UdtUnionV0(ScSpecUdtUnionV0 { doc, .. }) + | ScSpecEntry::UdtEnumV0(ScSpecUdtEnumV0 { doc, .. }) + | ScSpecEntry::UdtErrorEnumV0(ScSpecUdtErrorEnumV0 { doc, .. }) + | ScSpecEntry::EventV0(ScSpecEventV0 { doc, .. }) => doc, + }; + Ok(doc.to_utf8_string_lossy()) + } + + /// Finds the user-defined type entry a reference resolves to: by id when + /// the reference carries one, so the reference matches the entry exactly + /// even when types share a name, and by name otherwise. + /// + /// # Errors + /// + /// Might return errors + pub fn find_udt(&self, name: &str, id: Option<&[u8; 8]>) -> Result<&ScSpecEntry, Error> { + if let Some(id) = id { + let entry = self.0.as_ref().and_then(|specs| { + specs.iter().find(|e| match e { + ScSpecEntry::UdtStructV0(x) => &x.id == id, + ScSpecEntry::UdtUnionV0(x) => &x.id == id, + ScSpecEntry::UdtEnumV0(x) => &x.id == id, + ScSpecEntry::UdtErrorEnumV0(x) => &x.id == id, + ScSpecEntry::FunctionV0(_) | ScSpecEntry::EventV0(_) => false, + }) + }); + if let Some(entry) = entry { + return Ok(entry); + } + } + self.find(name) + } + /// # Errors /// /// Might return errors @@ -288,6 +317,14 @@ impl Spec { { Ok(Value::String(s.to_owned())) } + ScType::UdtV2(udt) + if matches!( + self.find_udt(&udt.name.to_utf8_string_lossy(), Some(&udt.id))?, + ScSpecEntry::UdtUnionV0(_) | ScSpecEntry::UdtStructV0(_) + ) => + { + Ok(Value::String(s.to_owned())) + } _ => Err(Error::Serde(e)), }, |val| match t { @@ -364,7 +401,12 @@ impl Spec { (ScType::Tuple(elem), Value::Array(raw)) => self.parse_tuple(t, elem, raw)?, // User defined types parsing - (ScType::Udt(ScSpecTypeUdt { name }), _) => self.parse_udt(name, v)?, + (ScType::Udt(ScSpecTypeUdt { name }), _) => { + self.parse_udt(&name.to_utf8_string_lossy(), None, v)? + } + (ScType::UdtV2(udt), _) => { + self.parse_udt(&udt.name.to_utf8_string_lossy(), Some(&udt.id), v)? + } // TODO: Implement the rest of these (_, raw) => serde_json::from_value(raw.clone()).map_err(Error::Serde)?, @@ -372,9 +414,8 @@ impl Spec { Ok(val) } - fn parse_udt(&self, name: &StringM<60>, value: &Value) -> Result { - let name = &name.to_utf8_string_lossy(); - match (self.find(name)?, value) { + fn parse_udt(&self, name: &str, id: Option<&[u8; 8]>, value: &Value) -> Result { + match (self.find_udt(name, id)?, value) { (ScSpecEntry::UdtStructV0(strukt), Value::Object(map)) => { if strukt .fields @@ -649,9 +690,13 @@ impl Spec { /// # Panics /// /// May panic - pub fn udt_to_json(&self, name: &StringM<60>, sc_obj: &ScVal) -> Result { - let name = &name.to_utf8_string_lossy(); - let udt = self.find(name)?; + pub fn udt_to_json( + &self, + name: &str, + id: Option<&[u8; 8]>, + sc_obj: &ScVal, + ) -> Result { + let udt = self.find_udt(name, id)?; Ok(match (sc_obj, udt) { (ScVal::Map(Some(map)), ScSpecEntry::UdtStructV0(strukt)) => serde_json::Value::Object( strukt @@ -757,7 +802,10 @@ impl Spec { ( sc_obj @ (ScVal::Vec(_) | ScVal::Map(_) | ScVal::U32(_)), ScType::Udt(ScSpecTypeUdt { name }), - ) => self.udt_to_json(name, sc_obj)?, + ) => self.udt_to_json(&name.to_utf8_string_lossy(), None, sc_obj)?, + (sc_obj @ (ScVal::Vec(_) | ScVal::Map(_) | ScVal::U32(_)), ScType::UdtV2(udt)) => { + self.udt_to_json(&udt.name.to_utf8_string_lossy(), Some(&udt.id), sc_obj)? + } (ScVal::Map(Some(map)), ScType::Map(map_type)) => self.sc_map_to_json(map, map_type)?, @@ -997,6 +1045,11 @@ pub fn to_json(v: &ScVal) -> Result { .map_err(|_| Error::InvalidValue(Some(ScType::Symbol)))? .to_string(), ), + ScVal::ExecutableTag(v) => Value::String( + std::str::from_utf8(v.as_slice()) + .map_err(|_| Error::InvalidValue(Some(ScType::String)))? + .to_string(), + ), ScVal::String(v) => Value::String( std::str::from_utf8(v.as_slice()) .map_err(|_| Error::InvalidValue(Some(ScType::Symbol)))? @@ -1094,6 +1147,14 @@ pub fn to_json(v: &ScVal) -> Result { executable: ContractExecutable::StellarAsset, .. }) => json!({"SAC": true}), + ScVal::ContractInstance(ScContractInstance { + executable: ContractExecutable::ExternalRef(external_ref), + .. + }) => json!({ + "executable_owner": sc_address_to_json(&external_ref.executable_owner), + "tag": std::str::from_utf8(external_ref.tag.as_slice()) + .map_err(|_| Error::InvalidValue(Some(ScType::String)))?, + }), ScVal::LedgerKeyNonce(ScNonceKey { nonce }) => { Value::Number(serde_json::Number::from(*nonce)) } @@ -1213,33 +1274,43 @@ impl Spec { Some(format!("Map<{key}, {val}>")) } ScType::BytesN(t) => Some(format!("{}_hex_bytes", t.n)), - ScType::Udt(ScSpecTypeUdt { name }) => { - match self.find(&name.to_utf8_string_lossy()).ok()? { - ScSpecEntry::UdtStructV0(ScSpecUdtStructV0 { fields, .. }) - if fields - .first() - .is_some_and(|f| f.name.to_utf8_string_lossy() == "0") => - { - let fields = fields - .iter() - .map(|t| self.arg_value_name(&t.type_, depth + 1)) - .collect::>>()? - .join(", "); - Some(format!("[{fields}]")) - } - ScSpecEntry::UdtStructV0(strukt) => self.arg_value_udt(strukt, depth), - ScSpecEntry::UdtUnionV0(union) => self.arg_value_union(union, depth), - ScSpecEntry::UdtEnumV0(enum_) => Some(arg_value_enum(enum_)), - ScSpecEntry::FunctionV0(_) - | ScSpecEntry::UdtErrorEnumV0(_) - | ScSpecEntry::EventV0(_) => None, - } - } + ScType::Udt(ScSpecTypeUdt { name }) => self.arg_value_name_udt( + self.find_udt(&name.to_utf8_string_lossy(), None).ok()?, + depth, + ), + ScType::UdtV2(udt) => self.arg_value_name_udt( + self.find_udt(&udt.name.to_utf8_string_lossy(), Some(&udt.id)) + .ok()?, + depth, + ), // No specific value name for these yet. ScType::Val => None, } } + fn arg_value_name_udt(&self, entry: &ScSpecEntry, depth: usize) -> Option { + match entry { + ScSpecEntry::UdtStructV0(ScSpecUdtStructV0 { fields, .. }) + if fields + .first() + .is_some_and(|f| f.name.to_utf8_string_lossy() == "0") => + { + let fields = fields + .iter() + .map(|t| self.arg_value_name(&t.type_, depth + 1)) + .collect::>>()? + .join(", "); + Some(format!("[{fields}]")) + } + ScSpecEntry::UdtStructV0(strukt) => self.arg_value_udt(strukt, depth), + ScSpecEntry::UdtUnionV0(union) => self.arg_value_union(union, depth), + ScSpecEntry::UdtEnumV0(enum_) => Some(arg_value_enum(enum_)), + ScSpecEntry::FunctionV0(_) + | ScSpecEntry::UdtErrorEnumV0(_) + | ScSpecEntry::EventV0(_) => None, + } + } + fn arg_value_udt(&self, strukt: &ScSpecUdtStructV0, depth: usize) -> Option { let inner = strukt .fields @@ -1272,8 +1343,14 @@ impl Spec { match &type_[0] { ScType::Vec(type_vec) => { let element_type = type_vec.element_type.clone(); - if let ScType::Udt(udt_type) = *element_type { - return Some(udt_type.name.to_utf8_string_lossy()); + match *element_type { + ScType::Udt(udt_type) => { + return Some(udt_type.name.to_utf8_string_lossy()); + } + ScType::UdtV2(udt_type) => { + return Some(udt_type.name.to_utf8_string_lossy()); + } + _ => {} } } _ => { @@ -1384,8 +1461,13 @@ impl Spec { Some(format!("\"{res}\"")) } ScType::Udt(ScSpecTypeUdt { name }) => { - self.example_udts(depth, name.to_utf8_string_lossy().as_ref()) + self.example_udts(depth, name.to_utf8_string_lossy().as_ref(), None) } + ScType::UdtV2(udt) => self.example_udts( + depth, + udt.name.to_utf8_string_lossy().as_ref(), + Some(&udt.id), + ), ScType::MuxedAddress => { Some("\"GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAWHF\"".to_string()) } @@ -1394,13 +1476,13 @@ impl Spec { } } - fn example_udts(&self, depth: usize, name: &str) -> Option { + fn example_udts(&self, depth: usize, name: &str, id: Option<&[u8; 8]>) -> Option { if depth > 2 { return Some(name.to_string()); } let depth = depth + 1; - let built = match self.find(name).ok() { + let built = match self.find_udt(name, id).ok() { Some(ScSpecEntry::UdtStructV0(strukt)) => { // Check if a tuple strukt and handle it just as a tuple going forward let build_struct = if !strukt.fields.is_empty() diff --git a/cmd/crates/soroban-spec-tools/src/verify.rs b/cmd/crates/soroban-spec-tools/src/verify.rs index b3af7405c7..e684ac6d51 100644 --- a/cmd/crates/soroban-spec-tools/src/verify.rs +++ b/cmd/crates/soroban-spec-tools/src/verify.rs @@ -235,6 +235,7 @@ mod tests { doc: StringM::default(), lib: StringM::default(), name: StringM::from_str(name).unwrap(), + id: [0; 8], fields: field_types .into_iter() .map(|(fname, ftype)| stellar_xdr::ScSpecUdtStructFieldV0 { diff --git a/cmd/crates/soroban-spec-typescript/src/types.rs b/cmd/crates/soroban-spec-typescript/src/types.rs index 62ff7456d2..bbcf58582a 100644 --- a/cmd/crates/soroban-spec-typescript/src/types.rs +++ b/cmd/crates/soroban-spec-typescript/src/types.rs @@ -198,6 +198,9 @@ impl From<&ScSpecTypeDef> for Type { ScSpecTypeDef::Udt(udt) => Type::Custom { name: udt.name.to_utf8_string_lossy(), }, + ScSpecTypeDef::UdtV2(udt) => Type::Custom { + name: udt.name.to_utf8_string_lossy(), + }, ScSpecTypeDef::BytesN(b) => Type::BytesN { n: b.n }, ScSpecTypeDef::Val => Type::Val, ScSpecTypeDef::U64 => Type::U64, diff --git a/cmd/crates/soroban-test/tests/fixtures/test-wasms/token/src/contract.rs b/cmd/crates/soroban-test/tests/fixtures/test-wasms/token/src/contract.rs index eac10e5751..8a30558a34 100644 --- a/cmd/crates/soroban-test/tests/fixtures/test-wasms/token/src/contract.rs +++ b/cmd/crates/soroban-test/tests/fixtures/test-wasms/token/src/contract.rs @@ -96,7 +96,7 @@ impl token::Interface for Token { from: from.clone(), spender: spender.clone(), amount, - expiration_ledger, + live_until_ledger: expiration_ledger, } .publish(&e); } diff --git a/cmd/crates/soroban-test/tests/it/build.rs b/cmd/crates/soroban-test/tests/it/build.rs index b5f7631ca6..872f624db4 100644 --- a/cmd/crates/soroban-test/tests/it/build.rs +++ b/cmd/crates/soroban-test/tests/it/build.rs @@ -416,6 +416,7 @@ fn filter_and_dedup_spec_removes_duplicates() { doc: StringM::default(), lib: StringM::default(), name: "MyStruct".try_into().unwrap(), + id: [0; 8], fields: vec![ScSpecUdtStructFieldV0 { doc: StringM::default(), name: "field".try_into().unwrap(), diff --git a/cmd/crates/soroban-test/tests/it/log.rs b/cmd/crates/soroban-test/tests/it/log.rs index d580e52296..f515e3ffd6 100644 --- a/cmd/crates/soroban-test/tests/it/log.rs +++ b/cmd/crates/soroban-test/tests/it/log.rs @@ -30,7 +30,7 @@ fn test_diagnostic_events_logging() { }); let captured_logs = logs.lock().unwrap(); - assert!(captured_logs.iter().any(|log| log.contains(r#"AAAAAAAAAAAAAAAAAAAAAgAAAAAAAAADAAAADwAAAAdmbl9jYWxsAAAAAA0AAAAgfKvD/pIJPlRnGd3RKaBZSHfoq/nJbJSYxkVTScSbhuYAAAAPAAAABGRlY3IAAAAB {"in_successful_contract_call":false,"event":{"ext":"v0","contract_id":null,"type_":"diagnostic","body":{"v0":{"topics":[{"symbol":"fn_call"},{"bytes":"7cabc3fe92093e546719ddd129a0594877e8abf9c96c9498c6455349c49b86e6"},{"symbol":"decr"}],"data":"void"}}}}"#))); + assert!(captured_logs.iter().any(|log| log.contains(r#"AAAAAAAAAAAAAAAAAAAAAgAAAAAAAAADAAAADwAAAAdmbl9jYWxsAAAAAA0AAAAgfKvD/pIJPlRnGd3RKaBZSHfoq/nJbJSYxkVTScSbhuYAAAAPAAAABGRlY3IAAAAB {"in_successful_contract_call":false,"event":{"ext":"v0","contract_id":null,"type":"diagnostic","body":{"v0":{"topics":[{"symbol":"fn_call"},{"bytes":"7cabc3fe92093e546719ddd129a0594877e8abf9c96c9498c6455349c49b86e6"},{"symbol":"decr"}],"data":"void"}}}}"#))); assert!(captured_logs .iter() .any(|log| log.contains("VM call trapped"))); diff --git a/cmd/soroban-cli/src/commands/contract/arg_parsing.rs b/cmd/soroban-cli/src/commands/contract/arg_parsing.rs index c97cfa551f..251b037106 100644 --- a/cmd/soroban-cli/src/commands/contract/arg_parsing.rs +++ b/cmd/soroban-cli/src/commands/contract/arg_parsing.rs @@ -531,6 +531,12 @@ fn get_type_name(type_def: &ScSpecTypeDef) -> String { sanitize(&udt.name.to_utf8_string_lossy()) ) } + ScSpecTypeDef::UdtV2(udt) => { + format!( + "user-defined type '{}'", + sanitize(&udt.name.to_utf8_string_lossy()) + ) + } } } @@ -627,11 +633,14 @@ fn parse_argument_with_validation( // Pre-validate JSON for non-primitive types, but skip for union (enum) UDTs since // both bare strings (e.g. `Unit`) and JSON strings (e.g. `"Unit"`) are valid for // unit variants — from_string in soroban-spec-tools handles both forms correctly. - let is_union_udt = if let ScSpecTypeDef::Udt(udt) = expected_type { - spec.find(&udt.name.to_utf8_string_lossy()) - .is_ok_and(|entry| matches!(entry, ScSpecEntry::UdtUnionV0(_))) - } else { - false + let is_union_udt = match expected_type { + ScSpecTypeDef::Udt(udt) => spec + .find(&udt.name.to_utf8_string_lossy()) + .is_ok_and(|entry| matches!(entry, ScSpecEntry::UdtUnionV0(_))), + ScSpecTypeDef::UdtV2(udt) => spec + .find_udt(&udt.name.to_utf8_string_lossy(), Some(&udt.id)) + .is_ok_and(|entry| matches!(entry, ScSpecEntry::UdtUnionV0(_))), + _ => false, }; if !is_primitive_type(expected_type) && !is_union_udt { validate_json_arg(arg_name, value)?; @@ -767,7 +776,22 @@ fn resolve_aliases_in_json( mutated |= resolve_aliases_in_json(value, &result.error_type, spec, config)?; } ScSpecTypeDef::Udt(udt) => { - mutated |= resolve_aliases_in_udt(value, udt, spec, config)?; + mutated |= resolve_aliases_in_udt( + value, + &udt.name.to_utf8_string_lossy(), + None, + spec, + config, + )?; + } + ScSpecTypeDef::UdtV2(udt) => { + mutated |= resolve_aliases_in_udt( + value, + &udt.name.to_utf8_string_lossy(), + Some(&udt.id), + spec, + config, + )?; } _ => {} } @@ -776,13 +800,13 @@ fn resolve_aliases_in_json( fn resolve_aliases_in_udt( value: &mut serde_json::Value, - udt: &stellar_xdr::ScSpecTypeUdt, + name: &str, + id: Option<&[u8; 8]>, spec: &Spec, config: &config::Args, ) -> Result { let mut mutated = false; - let name = udt.name.to_utf8_string_lossy(); - let Ok(entry) = spec.find(&name) else { + let Ok(entry) = spec.find_udt(name, id) else { return Ok(false); }; match entry { @@ -945,6 +969,7 @@ mod tests { doc: "".try_into().unwrap(), lib: "".try_into().unwrap(), name: "S".try_into().unwrap(), + id: [0; 8], fields: vec![ScSpecUdtStructFieldV0 { doc: "".try_into().unwrap(), name: "\x1b[2Jevil".try_into().unwrap(), @@ -1080,6 +1105,7 @@ mod tests { doc: StringM::default(), lib: StringM::default(), name: union_name.clone(), + id: [0; 8], cases: vec![ScSpecUdtUnionCaseV0::VoidV0(ScSpecUdtUnionCaseVoidV0 { doc: StringM::default(), name: case_name, @@ -1128,6 +1154,7 @@ mod tests { doc: StringM::default(), lib: StringM::default(), name: union_name.clone(), + id: [0; 8], cases: vec![ ScSpecUdtUnionCaseV0::VoidV0(ScSpecUdtUnionCaseVoidV0 { doc: StringM::default(), @@ -1203,6 +1230,7 @@ mod tests { doc: StringM::default(), lib: StringM::default(), name: struct_name.clone(), + id: [0; 8], fields: fields_xdr.try_into().unwrap(), })])); let ty = ScSpecTypeDef::Udt(ScSpecTypeUdt { name: struct_name }); @@ -1329,6 +1357,7 @@ mod tests { doc: StringM::default(), lib: StringM::default(), name: union_name.clone(), + id: [0; 8], cases: vec![ScSpecUdtUnionCaseV0::TupleV0(ScSpecUdtUnionCaseTupleV0 { doc: StringM::default(), name: "Pick".try_into().unwrap(), @@ -1367,6 +1396,7 @@ mod tests { doc: StringM::default(), lib: StringM::default(), name: union_name.clone(), + id: [0; 8], cases: vec![ScSpecUdtUnionCaseV0::TupleV0(ScSpecUdtUnionCaseTupleV0 { doc: StringM::default(), name: "Only".try_into().unwrap(), diff --git a/cmd/soroban-cli/src/get_spec.rs b/cmd/soroban-cli/src/get_spec.rs index a72069e7c5..3756caefa6 100644 --- a/cmd/soroban-cli/src/get_spec.rs +++ b/cmd/soroban-cli/src/get_spec.rs @@ -28,6 +28,11 @@ pub enum Error { Config(#[from] config::Error), #[error(transparent)] ContractSpec(#[from] contract_spec::Error), + #[error( + "cannot fetch spec for contract because the contract executable is \ + an external reference that does not have a downloadable code binary" + )] + ContractIsExternalRef, } /// @@ -76,5 +81,6 @@ pub async fn get_remote_contract_spec( ContractExecutable::StellarAsset => { soroban_spec::read::parse_raw(stellar_asset_spec::xdr())? } + ContractExecutable::ExternalRef(_) => return Err(Error::ContractIsExternalRef), }) } diff --git a/cmd/soroban-cli/src/log/auth.rs b/cmd/soroban-cli/src/log/auth.rs index fff3a5321f..0509369ed1 100644 --- a/cmd/soroban-cli/src/log/auth.rs +++ b/cmd/soroban-cli/src/log/auth.rs @@ -127,6 +127,15 @@ fn format_create_contract( ContractExecutable::StellarAsset => { let _ = writeln!(result, "{prefix} Executable: StellarAsset"); } + ContractExecutable::ExternalRef(external_ref) => { + let tag = std::str::from_utf8(external_ref.tag.as_ref()).unwrap_or(""); + let _ = writeln!( + result, + "{prefix} Executable: ExternalRef owner={} tag={}", + format_address(&external_ref.executable_owner), + sanitize(tag) + ); + } } if let Some(args) = constructor_args { if !args.is_empty() { diff --git a/cmd/soroban-cli/src/wasm.rs b/cmd/soroban-cli/src/wasm.rs index 406f001a04..6333ae1bc8 100644 --- a/cmd/soroban-cli/src/wasm.rs +++ b/cmd/soroban-cli/src/wasm.rs @@ -13,7 +13,7 @@ use crate::{ network::{Error as NetworkError, Network}, }, utils::{self, rpc::get_remote_wasm_from_hash}, - wasm::Error::{ContractIsStellarAsset, UnexpectedContractToken}, + wasm::Error::{ContractIsExternalRef, ContractIsStellarAsset, UnexpectedContractToken}, }; #[derive(thiserror::Error, Debug)] @@ -47,6 +47,11 @@ pub enum Error { a network built-in asset contract that does not have a downloadable code binary" )] ContractIsStellarAsset, + #[error( + "cannot fetch wasm for contract because the contract executable is \ + an external reference that does not have a downloadable code binary" + )] + ContractIsExternalRef, #[error(transparent)] Network(#[from] NetworkError), } @@ -133,6 +138,7 @@ pub async fn fetch_from_contract( return match &contract.executable { ContractExecutable::Wasm(hash) => Ok(get_remote_wasm_from_hash(&client, hash).await?), ContractExecutable::StellarAsset => Err(ContractIsStellarAsset), + ContractExecutable::ExternalRef(_) => Err(ContractIsExternalRef), }; } Err(UnexpectedContractToken(Box::new(data_entry))) @@ -158,6 +164,7 @@ pub async fn fetch_wasm_hash_from_contract( return match &contract.executable { ContractExecutable::Wasm(hash) => Ok(hash.clone()), ContractExecutable::StellarAsset => Err(ContractIsStellarAsset), + ContractExecutable::ExternalRef(_) => Err(ContractIsExternalRef), }; } Err(UnexpectedContractToken(Box::new(data_entry))) diff --git a/deny.toml b/deny.toml index dd05ae3b5b..0074885d51 100644 --- a/deny.toml +++ b/deny.toml @@ -231,7 +231,15 @@ 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 patches, remove once the following PRs are merged and released: + # https://github.com/stellar/rs-stellar-xdr/pull/568 + "https://github.com/stellar/rs-stellar-xdr?rev=182d524aa0cd34cd1b5d21ab71fbbebbd932939b", + # The rs-soroban-sdk PR stamping user-defined type spec entries with ids + "https://github.com/stellar/rs-soroban-sdk?rev=647dc7f29bc6151e21a2f1791e7954d260180161", + # https://github.com/stellar/rs-stellar-rpc-client/pull/108 + "https://github.com/stellar/rs-stellar-rpc-client?rev=e0d44a0b97959820e855eb9058e2114ac390c1fa", ] [sources.allow-org] From d8c08d303196701f44b824990722ca9a507e0545 Mon Sep 17 00:00:00 2001 From: Leigh <351529+leighmcculloch@users.noreply.github.com> Date: Mon, 10 Aug 2026 03:27:05 +0000 Subject: [PATCH 2/5] keep type id only on the udt v2 reference --- Cargo.lock | 18 ++-- Cargo.toml | 14 +-- cmd/crates/soroban-spec-tools/src/lib.rs | 89 ++++++------------- cmd/crates/soroban-spec-tools/src/verify.rs | 1 - cmd/crates/soroban-test/tests/it/build.rs | 1 - .../src/commands/contract/arg_parsing.rs | 29 ++---- deny.toml | 4 +- 7 files changed, 49 insertions(+), 107 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 79a035389f..0c6ba2d9c0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5570,7 +5570,7 @@ version = "27.1.0" [[package]] name = "soroban-ledger-snapshot" version = "27.0.5" -source = "git+https://github.com/stellar/rs-soroban-sdk?rev=647dc7f29bc6151e21a2f1791e7954d260180161#647dc7f29bc6151e21a2f1791e7954d260180161" +source = "git+https://github.com/stellar/rs-soroban-sdk?rev=db0de82715133c3732d422bdfcbee65f53c5c0c9#db0de82715133c3732d422bdfcbee65f53c5c0c9" dependencies = [ "serde", "serde_json", @@ -5583,7 +5583,7 @@ dependencies = [ [[package]] name = "soroban-sdk" version = "27.0.5" -source = "git+https://github.com/stellar/rs-soroban-sdk?rev=647dc7f29bc6151e21a2f1791e7954d260180161#647dc7f29bc6151e21a2f1791e7954d260180161" +source = "git+https://github.com/stellar/rs-soroban-sdk?rev=db0de82715133c3732d422bdfcbee65f53c5c0c9#db0de82715133c3732d422bdfcbee65f53c5c0c9" dependencies = [ "arbitrary", "bytes-lit", @@ -5607,7 +5607,7 @@ dependencies = [ [[package]] name = "soroban-sdk-macros" version = "27.0.5" -source = "git+https://github.com/stellar/rs-soroban-sdk?rev=647dc7f29bc6151e21a2f1791e7954d260180161#647dc7f29bc6151e21a2f1791e7954d260180161" +source = "git+https://github.com/stellar/rs-soroban-sdk?rev=db0de82715133c3732d422bdfcbee65f53c5c0c9#db0de82715133c3732d422bdfcbee65f53c5c0c9" dependencies = [ "darling 0.20.11", "heck 0.5.0", @@ -5625,7 +5625,7 @@ dependencies = [ [[package]] name = "soroban-spec" version = "27.0.5" -source = "git+https://github.com/stellar/rs-soroban-sdk?rev=647dc7f29bc6151e21a2f1791e7954d260180161#647dc7f29bc6151e21a2f1791e7954d260180161" +source = "git+https://github.com/stellar/rs-soroban-sdk?rev=db0de82715133c3732d422bdfcbee65f53c5c0c9#db0de82715133c3732d422bdfcbee65f53c5c0c9" dependencies = [ "base64 0.22.1", "sha2 0.10.9", @@ -5637,7 +5637,7 @@ dependencies = [ [[package]] name = "soroban-spec-rust" version = "27.0.5" -source = "git+https://github.com/stellar/rs-soroban-sdk?rev=647dc7f29bc6151e21a2f1791e7954d260180161#647dc7f29bc6151e21a2f1791e7954d260180161" +source = "git+https://github.com/stellar/rs-soroban-sdk?rev=db0de82715133c3732d422bdfcbee65f53c5c0c9#db0de82715133c3732d422bdfcbee65f53c5c0c9" dependencies = [ "prettyplease", "proc-macro2", @@ -5735,7 +5735,7 @@ dependencies = [ [[package]] name = "soroban-token-sdk" version = "27.0.5" -source = "git+https://github.com/stellar/rs-soroban-sdk?rev=647dc7f29bc6151e21a2f1791e7954d260180161#647dc7f29bc6151e21a2f1791e7954d260180161" +source = "git+https://github.com/stellar/rs-soroban-sdk?rev=db0de82715133c3732d422bdfcbee65f53c5c0c9#db0de82715133c3732d422bdfcbee65f53c5c0c9" dependencies = [ "soroban-sdk", ] @@ -5743,7 +5743,7 @@ dependencies = [ [[package]] name = "soroban-token-spec" version = "27.0.5" -source = "git+https://github.com/stellar/rs-soroban-sdk?rev=647dc7f29bc6151e21a2f1791e7954d260180161#647dc7f29bc6151e21a2f1791e7954d260180161" +source = "git+https://github.com/stellar/rs-soroban-sdk?rev=db0de82715133c3732d422bdfcbee65f53c5c0c9#db0de82715133c3732d422bdfcbee65f53c5c0c9" dependencies = [ "soroban-sdk", "soroban-token-sdk", @@ -5793,7 +5793,7 @@ checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" [[package]] name = "stellar-asset-spec" version = "27.0.5" -source = "git+https://github.com/stellar/rs-soroban-sdk?rev=647dc7f29bc6151e21a2f1791e7954d260180161#647dc7f29bc6151e21a2f1791e7954d260180161" +source = "git+https://github.com/stellar/rs-soroban-sdk?rev=db0de82715133c3732d422bdfcbee65f53c5c0c9#db0de82715133c3732d422bdfcbee65f53c5c0c9" dependencies = [ "soroban-sdk", "soroban-token-sdk", @@ -5896,7 +5896,7 @@ dependencies = [ [[package]] name = "stellar-xdr" version = "28.0.0" -source = "git+https://github.com/stellar/rs-stellar-xdr?rev=182d524aa0cd34cd1b5d21ab71fbbebbd932939b#182d524aa0cd34cd1b5d21ab71fbbebbd932939b" +source = "git+https://github.com/stellar/rs-stellar-xdr?rev=d7bf0331c0b212b97010b49c06d6da259c89588d#d7bf0331c0b212b97010b49c06d6da259c89588d" dependencies = [ "arbitrary", "base64 0.22.1", diff --git a/Cargo.toml b/Cargo.toml index eac75c9869..cd1956865f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -124,15 +124,15 @@ astral-tokio-tar = "0.6.0" # Pending https://github.com/stellar/rs-stellar-xdr/pull/568, which adds # user-defined type ids and the `ScSpecTypeUdtv2` reference type to the # contract spec. -stellar-xdr = { git = "https://github.com/stellar/rs-stellar-xdr", rev = "182d524aa0cd34cd1b5d21ab71fbbebbd932939b" } +stellar-xdr = { git = "https://github.com/stellar/rs-stellar-xdr", rev = "d7bf0331c0b212b97010b49c06d6da259c89588d" } # Pending the rs-soroban-sdk change that stamps every user-defined type's spec # entry with an id and marries references up with entries by id. -soroban-spec = { git = "https://github.com/stellar/rs-soroban-sdk", rev = "647dc7f29bc6151e21a2f1791e7954d260180161" } -soroban-spec-rust = { git = "https://github.com/stellar/rs-soroban-sdk", rev = "647dc7f29bc6151e21a2f1791e7954d260180161" } -soroban-sdk = { git = "https://github.com/stellar/rs-soroban-sdk", rev = "647dc7f29bc6151e21a2f1791e7954d260180161" } -soroban-token-sdk = { git = "https://github.com/stellar/rs-soroban-sdk", rev = "647dc7f29bc6151e21a2f1791e7954d260180161" } -stellar-asset-spec = { git = "https://github.com/stellar/rs-soroban-sdk", rev = "647dc7f29bc6151e21a2f1791e7954d260180161" } -soroban-ledger-snapshot = { git = "https://github.com/stellar/rs-soroban-sdk", rev = "647dc7f29bc6151e21a2f1791e7954d260180161" } +soroban-spec = { git = "https://github.com/stellar/rs-soroban-sdk", rev = "db0de82715133c3732d422bdfcbee65f53c5c0c9" } +soroban-spec-rust = { git = "https://github.com/stellar/rs-soroban-sdk", rev = "db0de82715133c3732d422bdfcbee65f53c5c0c9" } +soroban-sdk = { git = "https://github.com/stellar/rs-soroban-sdk", rev = "db0de82715133c3732d422bdfcbee65f53c5c0c9" } +soroban-token-sdk = { git = "https://github.com/stellar/rs-soroban-sdk", rev = "db0de82715133c3732d422bdfcbee65f53c5c0c9" } +stellar-asset-spec = { git = "https://github.com/stellar/rs-soroban-sdk", rev = "db0de82715133c3732d422bdfcbee65f53c5c0c9" } +soroban-ledger-snapshot = { git = "https://github.com/stellar/rs-soroban-sdk", rev = "db0de82715133c3732d422bdfcbee65f53c5c0c9" } # Pending https://github.com/stellar/rs-stellar-rpc-client/pull/108, which # updates the client to stellar-xdr 28. stellar-rpc-client = { git = "https://github.com/stellar/rs-stellar-rpc-client", rev = "e0d44a0b97959820e855eb9058e2114ac390c1fa" } diff --git a/cmd/crates/soroban-spec-tools/src/lib.rs b/cmd/crates/soroban-spec-tools/src/lib.rs index cba4c0b384..e08c0ba967 100644 --- a/cmd/crates/soroban-spec-tools/src/lib.rs +++ b/cmd/crates/soroban-spec-tools/src/lib.rs @@ -136,10 +136,8 @@ impl Spec { "Can be public key (G13..), a contract ID (C13...) or an identity (alice), ", ), ScType::Option(type_) => return self.doc(name, &type_.value_type), - ScType::Udt(ScSpecTypeUdt { name }) => self.udt_doc(&name.to_utf8_string_lossy(), None)?, - ScType::UdtV2(udt) => { - self.udt_doc(&udt.name.to_utf8_string_lossy(), Some(&udt.id))? - } + ScType::Udt(ScSpecTypeUdt { name }) => self.udt_doc(&name.to_utf8_string_lossy())?, + ScType::UdtV2(udt) => self.udt_doc(&udt.name.to_utf8_string_lossy())?, }; if let Some(mut ex) = self.example(0, type_) { @@ -162,8 +160,8 @@ impl Spec { } } - fn udt_doc(&self, name: &str, id: Option<&[u8; 8]>) -> Result { - let doc = match self.find_udt(name, id)? { + fn udt_doc(&self, name: &str) -> Result { + let doc = match self.find(name)? { ScSpecEntry::FunctionV0(ScSpecFunctionV0 { doc, .. }) | ScSpecEntry::UdtStructV0(ScSpecUdtStructV0 { doc, .. }) | ScSpecEntry::UdtUnionV0(ScSpecUdtUnionV0 { doc, .. }) @@ -174,31 +172,6 @@ impl Spec { Ok(doc.to_utf8_string_lossy()) } - /// Finds the user-defined type entry a reference resolves to: by id when - /// the reference carries one, so the reference matches the entry exactly - /// even when types share a name, and by name otherwise. - /// - /// # Errors - /// - /// Might return errors - pub fn find_udt(&self, name: &str, id: Option<&[u8; 8]>) -> Result<&ScSpecEntry, Error> { - if let Some(id) = id { - let entry = self.0.as_ref().and_then(|specs| { - specs.iter().find(|e| match e { - ScSpecEntry::UdtStructV0(x) => &x.id == id, - ScSpecEntry::UdtUnionV0(x) => &x.id == id, - ScSpecEntry::UdtEnumV0(x) => &x.id == id, - ScSpecEntry::UdtErrorEnumV0(x) => &x.id == id, - ScSpecEntry::FunctionV0(_) | ScSpecEntry::EventV0(_) => false, - }) - }); - if let Some(entry) = entry { - return Ok(entry); - } - } - self.find(name) - } - /// # Errors /// /// Might return errors @@ -319,7 +292,7 @@ impl Spec { } ScType::UdtV2(udt) if matches!( - self.find_udt(&udt.name.to_utf8_string_lossy(), Some(&udt.id))?, + self.find(&udt.name.to_utf8_string_lossy())?, ScSpecEntry::UdtUnionV0(_) | ScSpecEntry::UdtStructV0(_) ) => { @@ -402,11 +375,9 @@ impl Spec { // User defined types parsing (ScType::Udt(ScSpecTypeUdt { name }), _) => { - self.parse_udt(&name.to_utf8_string_lossy(), None, v)? - } - (ScType::UdtV2(udt), _) => { - self.parse_udt(&udt.name.to_utf8_string_lossy(), Some(&udt.id), v)? + self.parse_udt(&name.to_utf8_string_lossy(), v)? } + (ScType::UdtV2(udt), _) => self.parse_udt(&udt.name.to_utf8_string_lossy(), v)?, // TODO: Implement the rest of these (_, raw) => serde_json::from_value(raw.clone()).map_err(Error::Serde)?, @@ -414,8 +385,8 @@ impl Spec { Ok(val) } - fn parse_udt(&self, name: &str, id: Option<&[u8; 8]>, value: &Value) -> Result { - match (self.find_udt(name, id)?, value) { + fn parse_udt(&self, name: &str, value: &Value) -> Result { + match (self.find(name)?, value) { (ScSpecEntry::UdtStructV0(strukt), Value::Object(map)) => { if strukt .fields @@ -690,13 +661,8 @@ impl Spec { /// # Panics /// /// May panic - pub fn udt_to_json( - &self, - name: &str, - id: Option<&[u8; 8]>, - sc_obj: &ScVal, - ) -> Result { - let udt = self.find_udt(name, id)?; + pub fn udt_to_json(&self, name: &str, sc_obj: &ScVal) -> Result { + let udt = self.find(name)?; Ok(match (sc_obj, udt) { (ScVal::Map(Some(map)), ScSpecEntry::UdtStructV0(strukt)) => serde_json::Value::Object( strukt @@ -802,9 +768,9 @@ impl Spec { ( sc_obj @ (ScVal::Vec(_) | ScVal::Map(_) | ScVal::U32(_)), ScType::Udt(ScSpecTypeUdt { name }), - ) => self.udt_to_json(&name.to_utf8_string_lossy(), None, sc_obj)?, + ) => self.udt_to_json(&name.to_utf8_string_lossy(), sc_obj)?, (sc_obj @ (ScVal::Vec(_) | ScVal::Map(_) | ScVal::U32(_)), ScType::UdtV2(udt)) => { - self.udt_to_json(&udt.name.to_utf8_string_lossy(), Some(&udt.id), sc_obj)? + self.udt_to_json(&udt.name.to_utf8_string_lossy(), sc_obj)? } (ScVal::Map(Some(map)), ScType::Map(map_type)) => self.sc_map_to_json(map, map_type)?, @@ -1274,15 +1240,12 @@ impl Spec { Some(format!("Map<{key}, {val}>")) } ScType::BytesN(t) => Some(format!("{}_hex_bytes", t.n)), - ScType::Udt(ScSpecTypeUdt { name }) => self.arg_value_name_udt( - self.find_udt(&name.to_utf8_string_lossy(), None).ok()?, - depth, - ), - ScType::UdtV2(udt) => self.arg_value_name_udt( - self.find_udt(&udt.name.to_utf8_string_lossy(), Some(&udt.id)) - .ok()?, - depth, - ), + ScType::Udt(ScSpecTypeUdt { name }) => { + self.arg_value_name_udt(self.find(&name.to_utf8_string_lossy()).ok()?, depth) + } + ScType::UdtV2(udt) => { + self.arg_value_name_udt(self.find(&udt.name.to_utf8_string_lossy()).ok()?, depth) + } // No specific value name for these yet. ScType::Val => None, } @@ -1461,13 +1424,11 @@ impl Spec { Some(format!("\"{res}\"")) } ScType::Udt(ScSpecTypeUdt { name }) => { - self.example_udts(depth, name.to_utf8_string_lossy().as_ref(), None) + self.example_udts(depth, name.to_utf8_string_lossy().as_ref()) + } + ScType::UdtV2(udt) => { + self.example_udts(depth, udt.name.to_utf8_string_lossy().as_ref()) } - ScType::UdtV2(udt) => self.example_udts( - depth, - udt.name.to_utf8_string_lossy().as_ref(), - Some(&udt.id), - ), ScType::MuxedAddress => { Some("\"GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAWHF\"".to_string()) } @@ -1476,13 +1437,13 @@ impl Spec { } } - fn example_udts(&self, depth: usize, name: &str, id: Option<&[u8; 8]>) -> Option { + fn example_udts(&self, depth: usize, name: &str) -> Option { if depth > 2 { return Some(name.to_string()); } let depth = depth + 1; - let built = match self.find_udt(name, id).ok() { + let built = match self.find(name).ok() { Some(ScSpecEntry::UdtStructV0(strukt)) => { // Check if a tuple strukt and handle it just as a tuple going forward let build_struct = if !strukt.fields.is_empty() diff --git a/cmd/crates/soroban-spec-tools/src/verify.rs b/cmd/crates/soroban-spec-tools/src/verify.rs index e684ac6d51..b3af7405c7 100644 --- a/cmd/crates/soroban-spec-tools/src/verify.rs +++ b/cmd/crates/soroban-spec-tools/src/verify.rs @@ -235,7 +235,6 @@ mod tests { doc: StringM::default(), lib: StringM::default(), name: StringM::from_str(name).unwrap(), - id: [0; 8], fields: field_types .into_iter() .map(|(fname, ftype)| stellar_xdr::ScSpecUdtStructFieldV0 { diff --git a/cmd/crates/soroban-test/tests/it/build.rs b/cmd/crates/soroban-test/tests/it/build.rs index 872f624db4..b5f7631ca6 100644 --- a/cmd/crates/soroban-test/tests/it/build.rs +++ b/cmd/crates/soroban-test/tests/it/build.rs @@ -416,7 +416,6 @@ fn filter_and_dedup_spec_removes_duplicates() { doc: StringM::default(), lib: StringM::default(), name: "MyStruct".try_into().unwrap(), - id: [0; 8], fields: vec![ScSpecUdtStructFieldV0 { doc: StringM::default(), name: "field".try_into().unwrap(), diff --git a/cmd/soroban-cli/src/commands/contract/arg_parsing.rs b/cmd/soroban-cli/src/commands/contract/arg_parsing.rs index 251b037106..d369267143 100644 --- a/cmd/soroban-cli/src/commands/contract/arg_parsing.rs +++ b/cmd/soroban-cli/src/commands/contract/arg_parsing.rs @@ -638,7 +638,7 @@ fn parse_argument_with_validation( .find(&udt.name.to_utf8_string_lossy()) .is_ok_and(|entry| matches!(entry, ScSpecEntry::UdtUnionV0(_))), ScSpecTypeDef::UdtV2(udt) => spec - .find_udt(&udt.name.to_utf8_string_lossy(), Some(&udt.id)) + .find(&udt.name.to_utf8_string_lossy()) .is_ok_and(|entry| matches!(entry, ScSpecEntry::UdtUnionV0(_))), _ => false, }; @@ -776,22 +776,12 @@ fn resolve_aliases_in_json( mutated |= resolve_aliases_in_json(value, &result.error_type, spec, config)?; } ScSpecTypeDef::Udt(udt) => { - mutated |= resolve_aliases_in_udt( - value, - &udt.name.to_utf8_string_lossy(), - None, - spec, - config, - )?; + mutated |= + resolve_aliases_in_udt(value, &udt.name.to_utf8_string_lossy(), spec, config)?; } ScSpecTypeDef::UdtV2(udt) => { - mutated |= resolve_aliases_in_udt( - value, - &udt.name.to_utf8_string_lossy(), - Some(&udt.id), - spec, - config, - )?; + mutated |= + resolve_aliases_in_udt(value, &udt.name.to_utf8_string_lossy(), spec, config)?; } _ => {} } @@ -801,12 +791,11 @@ fn resolve_aliases_in_json( fn resolve_aliases_in_udt( value: &mut serde_json::Value, name: &str, - id: Option<&[u8; 8]>, spec: &Spec, config: &config::Args, ) -> Result { let mut mutated = false; - let Ok(entry) = spec.find_udt(name, id) else { + let Ok(entry) = spec.find(name) else { return Ok(false); }; match entry { @@ -969,7 +958,6 @@ mod tests { doc: "".try_into().unwrap(), lib: "".try_into().unwrap(), name: "S".try_into().unwrap(), - id: [0; 8], fields: vec![ScSpecUdtStructFieldV0 { doc: "".try_into().unwrap(), name: "\x1b[2Jevil".try_into().unwrap(), @@ -1105,7 +1093,6 @@ mod tests { doc: StringM::default(), lib: StringM::default(), name: union_name.clone(), - id: [0; 8], cases: vec![ScSpecUdtUnionCaseV0::VoidV0(ScSpecUdtUnionCaseVoidV0 { doc: StringM::default(), name: case_name, @@ -1154,7 +1141,6 @@ mod tests { doc: StringM::default(), lib: StringM::default(), name: union_name.clone(), - id: [0; 8], cases: vec![ ScSpecUdtUnionCaseV0::VoidV0(ScSpecUdtUnionCaseVoidV0 { doc: StringM::default(), @@ -1230,7 +1216,6 @@ mod tests { doc: StringM::default(), lib: StringM::default(), name: struct_name.clone(), - id: [0; 8], fields: fields_xdr.try_into().unwrap(), })])); let ty = ScSpecTypeDef::Udt(ScSpecTypeUdt { name: struct_name }); @@ -1357,7 +1342,6 @@ mod tests { doc: StringM::default(), lib: StringM::default(), name: union_name.clone(), - id: [0; 8], cases: vec![ScSpecUdtUnionCaseV0::TupleV0(ScSpecUdtUnionCaseTupleV0 { doc: StringM::default(), name: "Pick".try_into().unwrap(), @@ -1396,7 +1380,6 @@ mod tests { doc: StringM::default(), lib: StringM::default(), name: union_name.clone(), - id: [0; 8], cases: vec![ScSpecUdtUnionCaseV0::TupleV0(ScSpecUdtUnionCaseTupleV0 { doc: StringM::default(), name: "Only".try_into().unwrap(), diff --git a/deny.toml b/deny.toml index 0074885d51..5bd7c31e64 100644 --- a/deny.toml +++ b/deny.toml @@ -235,9 +235,9 @@ allow-git = [ # Temporary patches, remove once the following PRs are merged and released: # https://github.com/stellar/rs-stellar-xdr/pull/568 - "https://github.com/stellar/rs-stellar-xdr?rev=182d524aa0cd34cd1b5d21ab71fbbebbd932939b", + "https://github.com/stellar/rs-stellar-xdr?rev=d7bf0331c0b212b97010b49c06d6da259c89588d", # The rs-soroban-sdk PR stamping user-defined type spec entries with ids - "https://github.com/stellar/rs-soroban-sdk?rev=647dc7f29bc6151e21a2f1791e7954d260180161", + "https://github.com/stellar/rs-soroban-sdk?rev=db0de82715133c3732d422bdfcbee65f53c5c0c9", # https://github.com/stellar/rs-stellar-rpc-client/pull/108 "https://github.com/stellar/rs-stellar-rpc-client?rev=e0d44a0b97959820e855eb9058e2114ac390c1fa", ] From 7a13780f302bb570c77435dc02e65f4cba309b9e Mon Sep 17 00:00:00 2001 From: Leigh <351529+leighmcculloch@users.noreply.github.com> Date: Mon, 10 Aug 2026 05:58:10 +0000 Subject: [PATCH 3/5] resolve spec references by entry id --- Cargo.lock | 18 +- Cargo.toml | 14 +- cmd/crates/soroban-spec-tools/src/contract.rs | 12 +- cmd/crates/soroban-spec-tools/src/lib.rs | 162 +++++++++++++----- cmd/crates/soroban-spec-tools/src/verify.rs | 34 +++- .../soroban-spec-typescript/src/types.rs | 12 +- cmd/crates/soroban-test/tests/it/build.rs | 22 ++- .../src/commands/contract/arg_parsing.rs | 38 ++-- deny.toml | 4 +- 9 files changed, 228 insertions(+), 88 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0c6ba2d9c0..ae63bc80bf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5570,7 +5570,7 @@ version = "27.1.0" [[package]] name = "soroban-ledger-snapshot" version = "27.0.5" -source = "git+https://github.com/stellar/rs-soroban-sdk?rev=db0de82715133c3732d422bdfcbee65f53c5c0c9#db0de82715133c3732d422bdfcbee65f53c5c0c9" +source = "git+https://github.com/stellar/rs-soroban-sdk?rev=997dd6dbe9c0ce01a3c4cc940c392e9debab12dd#997dd6dbe9c0ce01a3c4cc940c392e9debab12dd" dependencies = [ "serde", "serde_json", @@ -5583,7 +5583,7 @@ dependencies = [ [[package]] name = "soroban-sdk" version = "27.0.5" -source = "git+https://github.com/stellar/rs-soroban-sdk?rev=db0de82715133c3732d422bdfcbee65f53c5c0c9#db0de82715133c3732d422bdfcbee65f53c5c0c9" +source = "git+https://github.com/stellar/rs-soroban-sdk?rev=997dd6dbe9c0ce01a3c4cc940c392e9debab12dd#997dd6dbe9c0ce01a3c4cc940c392e9debab12dd" dependencies = [ "arbitrary", "bytes-lit", @@ -5607,7 +5607,7 @@ dependencies = [ [[package]] name = "soroban-sdk-macros" version = "27.0.5" -source = "git+https://github.com/stellar/rs-soroban-sdk?rev=db0de82715133c3732d422bdfcbee65f53c5c0c9#db0de82715133c3732d422bdfcbee65f53c5c0c9" +source = "git+https://github.com/stellar/rs-soroban-sdk?rev=997dd6dbe9c0ce01a3c4cc940c392e9debab12dd#997dd6dbe9c0ce01a3c4cc940c392e9debab12dd" dependencies = [ "darling 0.20.11", "heck 0.5.0", @@ -5625,7 +5625,7 @@ dependencies = [ [[package]] name = "soroban-spec" version = "27.0.5" -source = "git+https://github.com/stellar/rs-soroban-sdk?rev=db0de82715133c3732d422bdfcbee65f53c5c0c9#db0de82715133c3732d422bdfcbee65f53c5c0c9" +source = "git+https://github.com/stellar/rs-soroban-sdk?rev=997dd6dbe9c0ce01a3c4cc940c392e9debab12dd#997dd6dbe9c0ce01a3c4cc940c392e9debab12dd" dependencies = [ "base64 0.22.1", "sha2 0.10.9", @@ -5637,7 +5637,7 @@ dependencies = [ [[package]] name = "soroban-spec-rust" version = "27.0.5" -source = "git+https://github.com/stellar/rs-soroban-sdk?rev=db0de82715133c3732d422bdfcbee65f53c5c0c9#db0de82715133c3732d422bdfcbee65f53c5c0c9" +source = "git+https://github.com/stellar/rs-soroban-sdk?rev=997dd6dbe9c0ce01a3c4cc940c392e9debab12dd#997dd6dbe9c0ce01a3c4cc940c392e9debab12dd" dependencies = [ "prettyplease", "proc-macro2", @@ -5735,7 +5735,7 @@ dependencies = [ [[package]] name = "soroban-token-sdk" version = "27.0.5" -source = "git+https://github.com/stellar/rs-soroban-sdk?rev=db0de82715133c3732d422bdfcbee65f53c5c0c9#db0de82715133c3732d422bdfcbee65f53c5c0c9" +source = "git+https://github.com/stellar/rs-soroban-sdk?rev=997dd6dbe9c0ce01a3c4cc940c392e9debab12dd#997dd6dbe9c0ce01a3c4cc940c392e9debab12dd" dependencies = [ "soroban-sdk", ] @@ -5743,7 +5743,7 @@ dependencies = [ [[package]] name = "soroban-token-spec" version = "27.0.5" -source = "git+https://github.com/stellar/rs-soroban-sdk?rev=db0de82715133c3732d422bdfcbee65f53c5c0c9#db0de82715133c3732d422bdfcbee65f53c5c0c9" +source = "git+https://github.com/stellar/rs-soroban-sdk?rev=997dd6dbe9c0ce01a3c4cc940c392e9debab12dd#997dd6dbe9c0ce01a3c4cc940c392e9debab12dd" dependencies = [ "soroban-sdk", "soroban-token-sdk", @@ -5793,7 +5793,7 @@ checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" [[package]] name = "stellar-asset-spec" version = "27.0.5" -source = "git+https://github.com/stellar/rs-soroban-sdk?rev=db0de82715133c3732d422bdfcbee65f53c5c0c9#db0de82715133c3732d422bdfcbee65f53c5c0c9" +source = "git+https://github.com/stellar/rs-soroban-sdk?rev=997dd6dbe9c0ce01a3c4cc940c392e9debab12dd#997dd6dbe9c0ce01a3c4cc940c392e9debab12dd" dependencies = [ "soroban-sdk", "soroban-token-sdk", @@ -5896,7 +5896,7 @@ dependencies = [ [[package]] name = "stellar-xdr" version = "28.0.0" -source = "git+https://github.com/stellar/rs-stellar-xdr?rev=d7bf0331c0b212b97010b49c06d6da259c89588d#d7bf0331c0b212b97010b49c06d6da259c89588d" +source = "git+https://github.com/stellar/rs-stellar-xdr?rev=2e565108988d9181c26477815e708ade929e67e0#2e565108988d9181c26477815e708ade929e67e0" dependencies = [ "arbitrary", "base64 0.22.1", diff --git a/Cargo.toml b/Cargo.toml index cd1956865f..e5fc66b44b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -124,15 +124,15 @@ astral-tokio-tar = "0.6.0" # Pending https://github.com/stellar/rs-stellar-xdr/pull/568, which adds # user-defined type ids and the `ScSpecTypeUdtv2` reference type to the # contract spec. -stellar-xdr = { git = "https://github.com/stellar/rs-stellar-xdr", rev = "d7bf0331c0b212b97010b49c06d6da259c89588d" } +stellar-xdr = { git = "https://github.com/stellar/rs-stellar-xdr", rev = "2e565108988d9181c26477815e708ade929e67e0" } # Pending the rs-soroban-sdk change that stamps every user-defined type's spec # entry with an id and marries references up with entries by id. -soroban-spec = { git = "https://github.com/stellar/rs-soroban-sdk", rev = "db0de82715133c3732d422bdfcbee65f53c5c0c9" } -soroban-spec-rust = { git = "https://github.com/stellar/rs-soroban-sdk", rev = "db0de82715133c3732d422bdfcbee65f53c5c0c9" } -soroban-sdk = { git = "https://github.com/stellar/rs-soroban-sdk", rev = "db0de82715133c3732d422bdfcbee65f53c5c0c9" } -soroban-token-sdk = { git = "https://github.com/stellar/rs-soroban-sdk", rev = "db0de82715133c3732d422bdfcbee65f53c5c0c9" } -stellar-asset-spec = { git = "https://github.com/stellar/rs-soroban-sdk", rev = "db0de82715133c3732d422bdfcbee65f53c5c0c9" } -soroban-ledger-snapshot = { git = "https://github.com/stellar/rs-soroban-sdk", rev = "db0de82715133c3732d422bdfcbee65f53c5c0c9" } +soroban-spec = { git = "https://github.com/stellar/rs-soroban-sdk", rev = "997dd6dbe9c0ce01a3c4cc940c392e9debab12dd" } +soroban-spec-rust = { git = "https://github.com/stellar/rs-soroban-sdk", rev = "997dd6dbe9c0ce01a3c4cc940c392e9debab12dd" } +soroban-sdk = { git = "https://github.com/stellar/rs-soroban-sdk", rev = "997dd6dbe9c0ce01a3c4cc940c392e9debab12dd" } +soroban-token-sdk = { git = "https://github.com/stellar/rs-soroban-sdk", rev = "997dd6dbe9c0ce01a3c4cc940c392e9debab12dd" } +stellar-asset-spec = { git = "https://github.com/stellar/rs-soroban-sdk", rev = "997dd6dbe9c0ce01a3c4cc940c392e9debab12dd" } +soroban-ledger-snapshot = { git = "https://github.com/stellar/rs-soroban-sdk", rev = "997dd6dbe9c0ce01a3c4cc940c392e9debab12dd" } # Pending https://github.com/stellar/rs-stellar-rpc-client/pull/108, which # updates the client to stellar-xdr 28. stellar-rpc-client = { git = "https://github.com/stellar/rs-stellar-rpc-client", rev = "e0d44a0b97959820e855eb9058e2114ac390c1fa" } diff --git a/cmd/crates/soroban-spec-tools/src/contract.rs b/cmd/crates/soroban-spec-tools/src/contract.rs index 2cc1343e75..ea0ae5169e 100644 --- a/cmd/crates/soroban-spec-tools/src/contract.rs +++ b/cmd/crates/soroban-spec-tools/src/contract.rs @@ -6,8 +6,8 @@ use std::{ use stellar_xdr::{ self as xdr, Limited, Limits, ReadXdr, ScEnvMetaEntry, ScEnvMetaEntryInterfaceVersion, - ScMetaEntry, ScMetaV0, ScSpecEntry, ScSpecFunctionV0, ScSpecUdtEnumV0, ScSpecUdtErrorEnumV0, - ScSpecUdtStructV0, ScSpecUdtUnionV0, StringM, WriteXdr, + ScMetaEntry, ScMetaV0, ScSpecEntry, ScSpecEntryV2Body, ScSpecFunctionV0, ScSpecUdtEnumV0, + ScSpecUdtErrorEnumV0, ScSpecUdtStructV0, ScSpecUdtUnionV0, StringM, WriteXdr, }; /// Maximum recursion depth allowed when decoding contract spec/meta sections. @@ -184,6 +184,14 @@ impl Display for Spec { ScSpecEntry::UdtEnumV0(udt) => write_enum(f, udt)?, ScSpecEntry::UdtErrorEnumV0(udt) => write_error(f, udt)?, ScSpecEntry::EventV0(_) => {} + ScSpecEntry::V2(v2) => match &v2.body { + ScSpecEntryV2Body::FunctionV0(func) => write_func(f, func)?, + ScSpecEntryV2Body::UdtUnionV0(udt) => write_union(f, udt)?, + ScSpecEntryV2Body::UdtStructV0(udt) => write_struct(f, udt)?, + ScSpecEntryV2Body::UdtEnumV0(udt) => write_enum(f, udt)?, + ScSpecEntryV2Body::UdtErrorEnumV0(udt) => write_error(f, udt)?, + ScSpecEntryV2Body::EventV0(_) => {} + }, } } } else { diff --git a/cmd/crates/soroban-spec-tools/src/lib.rs b/cmd/crates/soroban-spec-tools/src/lib.rs index e08c0ba967..c935e04815 100644 --- a/cmd/crates/soroban-spec-tools/src/lib.rs +++ b/cmd/crates/soroban-spec-tools/src/lib.rs @@ -1,4 +1,5 @@ #![allow(clippy::missing_errors_doc, clippy::must_use_candidate)] +use std::collections::HashMap; use std::fmt::Write; use std::str::FromStr; @@ -7,12 +8,12 @@ use serde_json::{json, Value}; use stellar_xdr::{ AccountId, BytesM, ContractExecutable, ContractId, Error as XdrError, Hash, Int128Parts, Int256Parts, MuxedEd25519Account, PublicKey, ScAddress, ScBytes, ScContractInstance, ScMap, - ScMapEntry, ScNonceKey, ScSpecEntry, ScSpecEventV0, ScSpecFunctionV0, ScSpecTypeDef as ScType, - ScSpecTypeMap, ScSpecTypeOption, ScSpecTypeResult, ScSpecTypeTuple, ScSpecTypeUdt, - ScSpecTypeVec, ScSpecUdtEnumV0, ScSpecUdtErrorEnumCaseV0, ScSpecUdtErrorEnumV0, - ScSpecUdtStructV0, ScSpecUdtUnionCaseTupleV0, ScSpecUdtUnionCaseV0, ScSpecUdtUnionCaseVoidV0, - ScSpecUdtUnionV0, ScString, ScSymbol, ScVal, ScVec, StringM, UInt128Parts, UInt256Parts, - Uint256, VecM, + ScMapEntry, ScNonceKey, ScSpecEntry, ScSpecEntryV2Body, ScSpecEventV0, ScSpecFunctionV0, + ScSpecTypeDef as ScType, ScSpecTypeMap, ScSpecTypeOption, ScSpecTypeResult, ScSpecTypeTuple, + ScSpecTypeUdt, ScSpecTypeUdtv2, ScSpecTypeVec, ScSpecUdtEnumV0, ScSpecUdtErrorEnumCaseV0, + ScSpecUdtErrorEnumV0, ScSpecUdtStructV0, ScSpecUdtUnionCaseTupleV0, ScSpecUdtUnionCaseV0, + ScSpecUdtUnionCaseVoidV0, ScSpecUdtUnionV0, ScString, ScSymbol, ScVal, ScVec, StringM, + UInt128Parts, UInt256Parts, Uint256, VecM, }; pub mod contract; @@ -75,7 +76,15 @@ pub enum Error { } #[derive(Default, Clone)] -pub struct Spec(pub Option>); +pub struct Spec { + /// The spec's entries, with every v2 entry replaced by its v0 body so + /// consumers match a single shape whichever form the spec stored. + pub entries: Option>, + /// The id each v2 entry carried, keyed to the index of its body in + /// `entries`. A reference carrying an id resolves to the entry whose id + /// matches, exactly, even when entries share a name. + ids: HashMap<[u8; 8], usize>, +} impl TryInto for &[u8] { type Error = soroban_spec::read::FromWasmError; @@ -88,7 +97,29 @@ impl TryInto for &[u8] { impl Spec { pub fn new(entries: &[ScSpecEntry]) -> Self { - Self(Some(entries.to_vec())) + let mut ids = HashMap::new(); + let entries = entries + .iter() + .enumerate() + .map(|(i, e)| match e { + ScSpecEntry::V2(v2) => { + ids.insert(v2.id, i); + match v2.body.clone() { + ScSpecEntryV2Body::FunctionV0(f) => ScSpecEntry::FunctionV0(f), + ScSpecEntryV2Body::UdtStructV0(s) => ScSpecEntry::UdtStructV0(s), + ScSpecEntryV2Body::UdtUnionV0(u) => ScSpecEntry::UdtUnionV0(u), + ScSpecEntryV2Body::UdtEnumV0(e) => ScSpecEntry::UdtEnumV0(e), + ScSpecEntryV2Body::UdtErrorEnumV0(e) => ScSpecEntry::UdtErrorEnumV0(e), + ScSpecEntryV2Body::EventV0(e) => ScSpecEntry::EventV0(e), + } + } + e => e.clone(), + }) + .collect(); + Self { + entries: Some(entries), + ids, + } } pub fn from_wasm(wasm: &[u8]) -> Result { @@ -136,8 +167,10 @@ impl Spec { "Can be public key (G13..), a contract ID (C13...) or an identity (alice), ", ), ScType::Option(type_) => return self.doc(name, &type_.value_type), - ScType::Udt(ScSpecTypeUdt { name }) => self.udt_doc(&name.to_utf8_string_lossy())?, - ScType::UdtV2(udt) => self.udt_doc(&udt.name.to_utf8_string_lossy())?, + ScType::Udt(ScSpecTypeUdt { name }) => { + Self::entry_doc(self.find(&name.to_utf8_string_lossy())?) + } + ScType::UdtV2(udt) => Self::entry_doc(self.find_udt_v2(udt)?), }; if let Some(mut ex) = self.example(0, type_) { @@ -160,23 +193,31 @@ impl Spec { } } - fn udt_doc(&self, name: &str) -> Result { - let doc = match self.find(name)? { + fn entry_doc(entry: &ScSpecEntry) -> String { + let doc = match entry { ScSpecEntry::FunctionV0(ScSpecFunctionV0 { doc, .. }) | ScSpecEntry::UdtStructV0(ScSpecUdtStructV0 { doc, .. }) | ScSpecEntry::UdtUnionV0(ScSpecUdtUnionV0 { doc, .. }) | ScSpecEntry::UdtEnumV0(ScSpecUdtEnumV0 { doc, .. }) | ScSpecEntry::UdtErrorEnumV0(ScSpecUdtErrorEnumV0 { doc, .. }) | ScSpecEntry::EventV0(ScSpecEventV0 { doc, .. }) => doc, + ScSpecEntry::V2(v2) => match &v2.body { + ScSpecEntryV2Body::FunctionV0(ScSpecFunctionV0 { doc, .. }) + | ScSpecEntryV2Body::UdtStructV0(ScSpecUdtStructV0 { doc, .. }) + | ScSpecEntryV2Body::UdtUnionV0(ScSpecUdtUnionV0 { doc, .. }) + | ScSpecEntryV2Body::UdtEnumV0(ScSpecUdtEnumV0 { doc, .. }) + | ScSpecEntryV2Body::UdtErrorEnumV0(ScSpecUdtErrorEnumV0 { doc, .. }) + | ScSpecEntryV2Body::EventV0(ScSpecEventV0 { doc, .. }) => doc, + }, }; - Ok(doc.to_utf8_string_lossy()) + doc.to_utf8_string_lossy() } /// # Errors /// /// Might return errors pub fn find(&self, name: &str) -> Result<&ScSpecEntry, Error> { - self.0 + self.entries .as_ref() .and_then(|specs| { specs.iter().find(|e| { @@ -187,6 +228,16 @@ impl Spec { ScSpecEntry::UdtEnumV0(x) => x.name.to_utf8_string_lossy(), ScSpecEntry::UdtErrorEnumV0(x) => x.name.to_utf8_string_lossy(), ScSpecEntry::EventV0(x) => x.name.to_utf8_string_lossy(), + // Entries are stored with v2 entries unwrapped to + // their bodies, but handle one anyway. + ScSpecEntry::V2(v2) => match &v2.body { + ScSpecEntryV2Body::FunctionV0(x) => x.name.to_utf8_string_lossy(), + ScSpecEntryV2Body::UdtStructV0(x) => x.name.to_utf8_string_lossy(), + ScSpecEntryV2Body::UdtUnionV0(x) => x.name.to_utf8_string_lossy(), + ScSpecEntryV2Body::UdtEnumV0(x) => x.name.to_utf8_string_lossy(), + ScSpecEntryV2Body::UdtErrorEnumV0(x) => x.name.to_utf8_string_lossy(), + ScSpecEntryV2Body::EventV0(x) => x.name.to_utf8_string_lossy(), + }, }; name == entry_name }) @@ -194,6 +245,26 @@ impl Spec { .ok_or_else(|| Error::MissingEntry(sanitize(name))) } + /// The entry carrying the given id, if any does. + pub fn find_by_id(&self, id: [u8; 8]) -> Option<&ScSpecEntry> { + let i = *self.ids.get(&id)?; + self.entries.as_ref()?.get(i) + } + + /// The entry a v2 user-defined type reference refers to: the entry whose + /// id matches when one does, or the entry the reference names, for specs + /// whose entries carry no ids. + /// + /// # Errors + /// + /// Might return errors + pub fn find_udt_v2(&self, udt: &ScSpecTypeUdtv2) -> Result<&ScSpecEntry, Error> { + if let Some(e) = self.find_by_id(udt.id) { + return Ok(e); + } + self.find(&udt.name.to_utf8_string_lossy()) + } + /// # Errors /// /// Might return errors @@ -208,7 +279,7 @@ impl Spec { /// pub fn find_functions(&self) -> Result, Error> { Ok(self - .0 + .entries .as_deref() .ok_or(Error::MissingSpec)? .iter() @@ -238,7 +309,7 @@ impl Spec { /// Returns an error if the spec is missing pub fn find_events(&self) -> Result, Error> { Ok(self - .0 + .entries .as_deref() .ok_or(Error::MissingSpec)? .iter() @@ -292,7 +363,7 @@ impl Spec { } ScType::UdtV2(udt) if matches!( - self.find(&udt.name.to_utf8_string_lossy())?, + self.find_udt_v2(udt)?, ScSpecEntry::UdtUnionV0(_) | ScSpecEntry::UdtStructV0(_) ) => { @@ -375,9 +446,9 @@ impl Spec { // User defined types parsing (ScType::Udt(ScSpecTypeUdt { name }), _) => { - self.parse_udt(&name.to_utf8_string_lossy(), v)? + self.parse_udt(self.find(&name.to_utf8_string_lossy())?, v)? } - (ScType::UdtV2(udt), _) => self.parse_udt(&udt.name.to_utf8_string_lossy(), v)?, + (ScType::UdtV2(udt), _) => self.parse_udt(self.find_udt_v2(udt)?, v)?, // TODO: Implement the rest of these (_, raw) => serde_json::from_value(raw.clone()).map_err(Error::Serde)?, @@ -385,8 +456,8 @@ impl Spec { Ok(val) } - fn parse_udt(&self, name: &str, value: &Value) -> Result { - match (self.find(name)?, value) { + fn parse_udt(&self, entry: &ScSpecEntry, value: &Value) -> Result { + match (entry, value) { (ScSpecEntry::UdtStructV0(strukt), Value::Object(map)) => { if strukt .fields @@ -661,8 +732,7 @@ impl Spec { /// # Panics /// /// May panic - pub fn udt_to_json(&self, name: &str, sc_obj: &ScVal) -> Result { - let udt = self.find(name)?; + pub fn udt_to_json(&self, udt: &ScSpecEntry, sc_obj: &ScVal) -> Result { Ok(match (sc_obj, udt) { (ScVal::Map(Some(map)), ScSpecEntry::UdtStructV0(strukt)) => serde_json::Value::Object( strukt @@ -768,9 +838,9 @@ impl Spec { ( sc_obj @ (ScVal::Vec(_) | ScVal::Map(_) | ScVal::U32(_)), ScType::Udt(ScSpecTypeUdt { name }), - ) => self.udt_to_json(&name.to_utf8_string_lossy(), sc_obj)?, + ) => self.udt_to_json(self.find(&name.to_utf8_string_lossy())?, sc_obj)?, (sc_obj @ (ScVal::Vec(_) | ScVal::Map(_) | ScVal::U32(_)), ScType::UdtV2(udt)) => { - self.udt_to_json(&udt.name.to_utf8_string_lossy(), sc_obj)? + self.udt_to_json(self.find_udt_v2(udt)?, sc_obj)? } (ScVal::Map(Some(map)), ScType::Map(map_type)) => self.sc_map_to_json(map, map_type)?, @@ -1243,9 +1313,7 @@ impl Spec { ScType::Udt(ScSpecTypeUdt { name }) => { self.arg_value_name_udt(self.find(&name.to_utf8_string_lossy()).ok()?, depth) } - ScType::UdtV2(udt) => { - self.arg_value_name_udt(self.find(&udt.name.to_utf8_string_lossy()).ok()?, depth) - } + ScType::UdtV2(udt) => self.arg_value_name_udt(self.find_udt_v2(udt).ok()?, depth), // No specific value name for these yet. ScType::Val => None, } @@ -1270,7 +1338,8 @@ impl Spec { ScSpecEntry::UdtEnumV0(enum_) => Some(arg_value_enum(enum_)), ScSpecEntry::FunctionV0(_) | ScSpecEntry::UdtErrorEnumV0(_) - | ScSpecEntry::EventV0(_) => None, + | ScSpecEntry::EventV0(_) + | ScSpecEntry::V2(_) => None, } } @@ -1424,11 +1493,14 @@ impl Spec { Some(format!("\"{res}\"")) } ScType::Udt(ScSpecTypeUdt { name }) => { - self.example_udts(depth, name.to_utf8_string_lossy().as_ref()) - } - ScType::UdtV2(udt) => { - self.example_udts(depth, udt.name.to_utf8_string_lossy().as_ref()) + let name = name.to_utf8_string_lossy(); + self.example_udts(depth, &name, self.find(&name).ok()) } + ScType::UdtV2(udt) => self.example_udts( + depth, + udt.name.to_utf8_string_lossy().as_ref(), + self.find_udt_v2(udt).ok(), + ), ScType::MuxedAddress => { Some("\"GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAWHF\"".to_string()) } @@ -1437,13 +1509,18 @@ impl Spec { } } - fn example_udts(&self, depth: usize, name: &str) -> Option { + fn example_udts( + &self, + depth: usize, + name: &str, + entry: Option<&ScSpecEntry>, + ) -> Option { if depth > 2 { return Some(name.to_string()); } let depth = depth + 1; - let built = match self.find(name).ok() { + let built = match entry { Some(ScSpecEntry::UdtStructV0(strukt)) => { // Check if a tuple strukt and handle it just as a tuple going forward let build_struct = if !strukt.fields.is_empty() @@ -1483,7 +1560,8 @@ impl Spec { Some( ScSpecEntry::FunctionV0(_) | ScSpecEntry::UdtErrorEnumV0(_) - | ScSpecEntry::EventV0(_), + | ScSpecEntry::EventV0(_) + | ScSpecEntry::V2(_), ) | None => None, }; @@ -2478,7 +2556,7 @@ mod tests { // Map: Symbol keys should appear as plain strings in JSON output, // not double-encoded as "\"bar\"" (regression for // https://github.com/stellar/stellar-cli/issues/2421). - let spec = Spec(None); + let spec = Spec::default(); let map_type = ScSpecTypeMap { key_type: Box::new(ScType::Symbol), value_type: Box::new(ScType::U32), @@ -2496,7 +2574,7 @@ mod tests { #[test] fn test_sc_map_to_json_string_keys_not_double_encoded() { // Map: String keys should also appear as plain strings in JSON output. - let spec = Spec(None); + let spec = Spec::default(); let map_type = ScSpecTypeMap { key_type: Box::new(ScType::String), value_type: Box::new(ScType::U32), @@ -2517,7 +2595,7 @@ mod tests { // 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 spec = Spec::default(); let bytes_val = ScVal::Bytes(ScBytes( vec![ 0x05, 0x5e, 0xf8, 0x16, 0x22, 0x3e, 0xe5, 0x21, 0x6b, 0x18, 0xc2, 0xdf, 0x00, 0xd6, @@ -2539,7 +2617,7 @@ mod tests { #[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 spec = Spec::default(); let map_val = ScVal::Map(Some( ScMap::sorted_from(vec![ScMapEntry { key: ScVal::Symbol(ScSymbol("key".try_into().unwrap())), @@ -2554,7 +2632,7 @@ mod tests { #[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 spec = Spec::default(); let vec_val = ScVal::Vec(Some( ScVec::try_from(vec![ScVal::U32(1), ScVal::U32(2)]).unwrap(), )); @@ -2565,7 +2643,7 @@ mod tests { #[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 spec = Spec::default(); let result = spec.xdr_to_json(&ScVal::U32(100), &ScType::Val); assert_eq!(result.unwrap(), Value::Number(100.into())); } diff --git a/cmd/crates/soroban-spec-tools/src/verify.rs b/cmd/crates/soroban-spec-tools/src/verify.rs index b3af7405c7..4d5a08ff16 100644 --- a/cmd/crates/soroban-spec-tools/src/verify.rs +++ b/cmd/crates/soroban-spec-tools/src/verify.rs @@ -1,8 +1,8 @@ use std::collections::HashSet; use stellar_xdr::{ - ScSpecEntry, ScSpecTypeDef as ScType, ScSpecTypeUdt, ScSpecUdtUnionCaseTupleV0, - ScSpecUdtUnionCaseV0, + ScSpecEntry, ScSpecEntryV2Body, ScSpecTypeDef as ScType, ScSpecTypeUdt, + ScSpecUdtUnionCaseTupleV0, ScSpecUdtUnionCaseV0, }; use crate::{sanitize, Spec}; @@ -52,6 +52,14 @@ fn entry_name(entry: &ScSpecEntry) -> String { ScSpecEntry::UdtEnumV0(x) => x.name.to_utf8_string_lossy(), ScSpecEntry::UdtErrorEnumV0(x) => x.name.to_utf8_string_lossy(), ScSpecEntry::EventV0(x) => x.name.to_utf8_string_lossy(), + ScSpecEntry::V2(v2) => match &v2.body { + ScSpecEntryV2Body::FunctionV0(x) => x.name.to_utf8_string_lossy(), + ScSpecEntryV2Body::UdtStructV0(x) => x.name.to_utf8_string_lossy(), + ScSpecEntryV2Body::UdtUnionV0(x) => x.name.to_utf8_string_lossy(), + ScSpecEntryV2Body::UdtEnumV0(x) => x.name.to_utf8_string_lossy(), + ScSpecEntryV2Body::UdtErrorEnumV0(x) => x.name.to_utf8_string_lossy(), + ScSpecEntryV2Body::EventV0(x) => x.name.to_utf8_string_lossy(), + }, } } @@ -63,7 +71,7 @@ impl Spec { /// - Undefined types: UDT names referenced in function signatures, event /// params, struct fields, or union cases that are not defined in the spec. pub fn verify(&self) -> Vec { - let Some(entries) = &self.0 else { + let Some(entries) = &self.entries else { return vec![]; }; @@ -85,6 +93,15 @@ impl Spec { | ScSpecEntry::UdtErrorEnumV0(_) => { defined.insert(name); } + ScSpecEntry::V2(v2) => match &v2.body { + ScSpecEntryV2Body::UdtStructV0(_) + | ScSpecEntryV2Body::UdtUnionV0(_) + | ScSpecEntryV2Body::UdtEnumV0(_) + | ScSpecEntryV2Body::UdtErrorEnumV0(_) => { + defined.insert(name); + } + ScSpecEntryV2Body::FunctionV0(_) | ScSpecEntryV2Body::EventV0(_) => {} + }, ScSpecEntry::FunctionV0(_) | ScSpecEntry::EventV0(_) => {} } } @@ -188,6 +205,17 @@ fn find_undefined_types( } } ScSpecEntry::UdtEnumV0(_) | ScSpecEntry::UdtErrorEnumV0(_) => {} + ScSpecEntry::V2(v2) => { + let body = match v2.body.clone() { + ScSpecEntryV2Body::FunctionV0(f) => ScSpecEntry::FunctionV0(f), + ScSpecEntryV2Body::UdtStructV0(s) => ScSpecEntry::UdtStructV0(s), + ScSpecEntryV2Body::UdtUnionV0(u) => ScSpecEntry::UdtUnionV0(u), + ScSpecEntryV2Body::UdtEnumV0(e) => ScSpecEntry::UdtEnumV0(e), + ScSpecEntryV2Body::UdtErrorEnumV0(e) => ScSpecEntry::UdtErrorEnumV0(e), + ScSpecEntryV2Body::EventV0(e) => ScSpecEntry::EventV0(e), + }; + find_undefined_types(&body, defined, warnings); + } } } diff --git a/cmd/crates/soroban-spec-typescript/src/types.rs b/cmd/crates/soroban-spec-typescript/src/types.rs index bbcf58582a..a9bbe05d69 100644 --- a/cmd/crates/soroban-spec-typescript/src/types.rs +++ b/cmd/crates/soroban-spec-typescript/src/types.rs @@ -1,6 +1,6 @@ use serde::Serialize; use stellar_xdr::{ - ScSpecEntry, ScSpecFunctionInputV0, ScSpecTypeDef, ScSpecUdtEnumCaseV0, + ScSpecEntry, ScSpecEntryV2Body, ScSpecFunctionInputV0, ScSpecTypeDef, ScSpecUdtEnumCaseV0, ScSpecUdtErrorEnumCaseV0, ScSpecUdtStructFieldV0, ScSpecUdtStructV0, ScSpecUdtUnionCaseV0, }; @@ -266,6 +266,16 @@ impl From<&ScSpecEntry> for Entry { ), name: e.name.to_utf8_string_lossy(), }, + // A v2 entry is its v0 body plus the id that identifies the + // entry; the generated bindings work from the body alone. + ScSpecEntry::V2(v2) => match v2.body.clone() { + ScSpecEntryV2Body::FunctionV0(f) => (&ScSpecEntry::FunctionV0(f)).into(), + ScSpecEntryV2Body::UdtStructV0(s) => (&ScSpecEntry::UdtStructV0(s)).into(), + ScSpecEntryV2Body::UdtUnionV0(u) => (&ScSpecEntry::UdtUnionV0(u)).into(), + ScSpecEntryV2Body::UdtEnumV0(e) => (&ScSpecEntry::UdtEnumV0(e)).into(), + ScSpecEntryV2Body::UdtErrorEnumV0(e) => (&ScSpecEntry::UdtErrorEnumV0(e)).into(), + ScSpecEntryV2Body::EventV0(e) => (&ScSpecEntry::EventV0(e)).into(), + }, } } } diff --git a/cmd/crates/soroban-test/tests/it/build.rs b/cmd/crates/soroban-test/tests/it/build.rs index b5f7631ca6..29f1354c39 100644 --- a/cmd/crates/soroban-test/tests/it/build.rs +++ b/cmd/crates/soroban-test/tests/it/build.rs @@ -2,7 +2,9 @@ use assert_fs::TempDir; use fs_extra::dir::CopyOptions; use predicates::prelude::{predicate, PredicateBooleanExt}; use shell_escape::escape; -use soroban_cli::xdr::{Limited, Limits, ReadXdr, ScMetaEntry, ScMetaV0, ScSpecEntry}; +use soroban_cli::xdr::{ + Limited, Limits, ReadXdr, ScMetaEntry, ScMetaV0, ScSpecEntry, ScSpecEntryV2Body, +}; use soroban_spec_tools::contract::Spec; use soroban_test::TestEnv; use std::env; @@ -310,6 +312,14 @@ fn spec_entry_name(entry: &ScSpecEntry) -> String { ScSpecEntry::UdtEnumV0(e) => e.name.to_utf8_string_lossy(), ScSpecEntry::UdtErrorEnumV0(e) => e.name.to_utf8_string_lossy(), ScSpecEntry::EventV0(e) => e.name.to_utf8_string_lossy(), + ScSpecEntry::V2(v2) => match &v2.body { + ScSpecEntryV2Body::FunctionV0(f) => f.name.to_utf8_string_lossy(), + ScSpecEntryV2Body::UdtStructV0(s) => s.name.to_utf8_string_lossy(), + ScSpecEntryV2Body::UdtUnionV0(u) => u.name.to_utf8_string_lossy(), + ScSpecEntryV2Body::UdtEnumV0(e) => e.name.to_utf8_string_lossy(), + ScSpecEntryV2Body::UdtErrorEnumV0(e) => e.name.to_utf8_string_lossy(), + ScSpecEntryV2Body::EventV0(e) => e.name.to_utf8_string_lossy(), + }, } } @@ -380,7 +390,10 @@ fn build_with_spec_shaking_preserves_all_functions() { let (spec, _meta) = build_spec_shaking_fixture(); let function_names: Vec = spec .iter() - .filter(|e| matches!(e, ScSpecEntry::FunctionV0(_))) + .filter(|e| { + matches!(e, ScSpecEntry::FunctionV0(_)) + || matches!(e, ScSpecEntry::V2(v2) if matches!(v2.body, ScSpecEntryV2Body::FunctionV0(_))) + }) .map(spec_entry_name) .collect(); @@ -504,7 +517,10 @@ fn build_without_spec_shaking_preserves_all_entries() { let function_names: Vec = spec .spec .iter() - .filter(|e| matches!(e, ScSpecEntry::FunctionV0(_))) + .filter(|e| { + matches!(e, ScSpecEntry::FunctionV0(_)) + || matches!(e, ScSpecEntry::V2(v2) if matches!(v2.body, ScSpecEntryV2Body::FunctionV0(_))) + }) .map(spec_entry_name) .collect(); assert!( diff --git a/cmd/soroban-cli/src/commands/contract/arg_parsing.rs b/cmd/soroban-cli/src/commands/contract/arg_parsing.rs index d369267143..3efc8c0602 100644 --- a/cmd/soroban-cli/src/commands/contract/arg_parsing.rs +++ b/cmd/soroban-cli/src/commands/contract/arg_parsing.rs @@ -118,7 +118,7 @@ fn build_host_function_parameters_with_filter( config: &config::Args, filter_constructor: bool, ) -> Result { - let spec = Spec(Some(spec_entries.to_vec())); + let spec = Spec::new(spec_entries); let cmd = build_clap_command(&spec, filter_constructor)?; let (function, matches_) = parse_command_matches(cmd, slop)?; let func = get_function_spec(&spec, &function)?; @@ -981,7 +981,7 @@ mod tests { outputs: vec![].try_into().unwrap(), }); - let spec = Spec(Some(vec![strukt, func])); + let spec = Spec::new(&[strukt, func]); let cmd = build_custom_cmd("f", &spec).unwrap(); let arg = cmd @@ -1089,7 +1089,7 @@ mod tests { // Build a minimal Spec with a union type: enum MyEnum { Unit } let union_name: StringM<60> = "MyEnum".try_into().unwrap(); let case_name: StringM<60> = "Unit".try_into().unwrap(); - let spec = Spec(Some(vec![ScSpecEntry::UdtUnionV0(ScSpecUdtUnionV0 { + let spec = Spec::new(&[ScSpecEntry::UdtUnionV0(ScSpecUdtUnionV0 { doc: StringM::default(), lib: StringM::default(), name: union_name.clone(), @@ -1099,7 +1099,7 @@ mod tests { })] .try_into() .unwrap(), - })])); + })]); let expected_type = ScSpecTypeDef::Udt(ScSpecTypeUdt { name: union_name }); let config = crate::config::Args::default(); @@ -1137,7 +1137,7 @@ mod tests { }; let union_name: StringM<60> = "MyEnum".try_into().unwrap(); - let spec = Spec(Some(vec![ScSpecEntry::UdtUnionV0(ScSpecUdtUnionV0 { + let spec = Spec::new(&[ScSpecEntry::UdtUnionV0(ScSpecUdtUnionV0 { doc: StringM::default(), lib: StringM::default(), name: union_name.clone(), @@ -1154,7 +1154,7 @@ mod tests { ] .try_into() .unwrap(), - })])); + })]); let expected_type = ScSpecTypeDef::Udt(ScSpecTypeUdt { name: union_name }); let config = crate::config::Args::default(); @@ -1212,12 +1212,12 @@ mod tests { type_: t.clone(), }) .collect(); - let spec = Spec(Some(vec![ScSpecEntry::UdtStructV0(ScSpecUdtStructV0 { + let spec = Spec::new(&[ScSpecEntry::UdtStructV0(ScSpecUdtStructV0 { doc: StringM::default(), lib: StringM::default(), name: struct_name.clone(), fields: fields_xdr.try_into().unwrap(), - })])); + })]); let ty = ScSpecTypeDef::Udt(ScSpecTypeUdt { name: struct_name }); (spec, ty) } @@ -1228,7 +1228,7 @@ mod tests { #[test] fn resolve_aliases_resolves_native_to_asset_contract_address() { let ty = ScSpecTypeDef::Address; - let spec = Spec(Some(vec![])); + let spec = Spec::new(&[]); let config = crate::config::Args::default(); let mut value = serde_json::json!("native"); @@ -1256,7 +1256,7 @@ mod tests { let ty = ScSpecTypeDef::Vec(Box::new(ScSpecTypeVec { element_type: Box::new(ScSpecTypeDef::Address), })); - let spec = Spec(Some(vec![])); + let spec = Spec::new(&[]); let config = crate::config::Args::default(); let mut value = serde_json::json!([TEST_G_ADDRESS]); @@ -1281,7 +1281,7 @@ mod tests { .try_into() .unwrap(), })); - let spec = Spec(Some(vec![])); + let spec = Spec::new(&[]); let config = crate::config::Args::default(); let mut value = serde_json::json!([TEST_G_ADDRESS, 42]); @@ -1338,7 +1338,7 @@ mod tests { }; let union_name: StringM<60> = "Choice".try_into().unwrap(); - let spec = Spec(Some(vec![ScSpecEntry::UdtUnionV0(ScSpecUdtUnionV0 { + let spec = Spec::new(&[ScSpecEntry::UdtUnionV0(ScSpecUdtUnionV0 { doc: StringM::default(), lib: StringM::default(), name: union_name.clone(), @@ -1351,7 +1351,7 @@ mod tests { })] .try_into() .unwrap(), - })])); + })]); let ty = ScSpecTypeDef::Udt(ScSpecTypeUdt { name: union_name }); let config = crate::config::Args::default(); @@ -1376,7 +1376,7 @@ mod tests { }; let union_name: StringM<60> = "OneOf".try_into().unwrap(); - let spec = Spec(Some(vec![ScSpecEntry::UdtUnionV0(ScSpecUdtUnionV0 { + let spec = Spec::new(&[ScSpecEntry::UdtUnionV0(ScSpecUdtUnionV0 { doc: StringM::default(), lib: StringM::default(), name: union_name.clone(), @@ -1387,7 +1387,7 @@ mod tests { })] .try_into() .unwrap(), - })])); + })]); let ty = ScSpecTypeDef::Udt(ScSpecTypeUdt { name: union_name }); let config = crate::config::Args::default(); @@ -1412,7 +1412,7 @@ mod tests { let opt_ty = ScSpecTypeDef::Option(Box::new(ScSpecTypeOption { value_type: Box::new(ScSpecTypeDef::Address), })); - let spec = Spec(Some(vec![])); + let spec = Spec::new(&[]); let config = crate::config::Args::default(); let mut value = serde_json::Value::Null; @@ -1447,7 +1447,7 @@ mod tests { ok_type: Box::new(ScSpecTypeDef::Address), error_type: Box::new(ScSpecTypeDef::U32), })); - let spec = Spec(Some(vec![])); + let spec = Spec::new(&[]); let config = crate::config::Args::default(); let mut value = serde_json::json!(TEST_G_ADDRESS); @@ -1483,7 +1483,7 @@ mod tests { let ty = ScSpecTypeDef::Vec(Box::new(ScSpecTypeVec { element_type: Box::new(ScSpecTypeDef::Address), })); - let spec = Spec(Some(vec![])); + let spec = Spec::new(&[]); let pretty = format!(r#"[ "{TEST_G_ADDRESS}" ]"#); assert_eq!( resolve_aliases(&pretty, &ty, &spec, &config).unwrap(), @@ -1499,7 +1499,7 @@ mod tests { key_type: Box::new(ScSpecTypeDef::Address), value_type: Box::new(ScSpecTypeDef::U32), })); - let spec = Spec(Some(vec![])); + let spec = Spec::new(&[]); let config = crate::config::Args::default(); let mut value = serde_json::json!({ TEST_G_ADDRESS: 1 }); diff --git a/deny.toml b/deny.toml index 5bd7c31e64..6f252ba792 100644 --- a/deny.toml +++ b/deny.toml @@ -235,9 +235,9 @@ allow-git = [ # Temporary patches, remove once the following PRs are merged and released: # https://github.com/stellar/rs-stellar-xdr/pull/568 - "https://github.com/stellar/rs-stellar-xdr?rev=d7bf0331c0b212b97010b49c06d6da259c89588d", + "https://github.com/stellar/rs-stellar-xdr?rev=2e565108988d9181c26477815e708ade929e67e0", # The rs-soroban-sdk PR stamping user-defined type spec entries with ids - "https://github.com/stellar/rs-soroban-sdk?rev=db0de82715133c3732d422bdfcbee65f53c5c0c9", + "https://github.com/stellar/rs-soroban-sdk?rev=997dd6dbe9c0ce01a3c4cc940c392e9debab12dd", # https://github.com/stellar/rs-stellar-rpc-client/pull/108 "https://github.com/stellar/rs-stellar-rpc-client?rev=e0d44a0b97959820e855eb9058e2114ac390c1fa", ] From 4cf2687effc71efd03402457430b2bc98e17616b Mon Sep 17 00:00:00 2001 From: Leigh <351529+leighmcculloch@users.noreply.github.com> Date: Mon, 10 Aug 2026 06:12:17 +0000 Subject: [PATCH 4/5] run CI quickstart at protocol 28 --- .github/workflows/bindings-ts.yml | 2 +- .github/workflows/ledger-emulator.yml | 2 +- .github/workflows/rpc-tests.yml | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/bindings-ts.yml b/.github/workflows/bindings-ts.yml index b93db71333..f07357c0a4 100644 --- a/.github/workflows/bindings-ts.yml +++ b/.github/workflows/bindings-ts.yml @@ -27,7 +27,7 @@ jobs: - uses: stellar/quickstart@main with: tag: nightly - protocol_version: 27 + protocol_version: 28 - uses: actions/setup-node@v7 with: node-version: "20.x" diff --git a/.github/workflows/ledger-emulator.yml b/.github/workflows/ledger-emulator.yml index b48b953c31..c01cae2ffc 100644 --- a/.github/workflows/ledger-emulator.yml +++ b/.github/workflows/ledger-emulator.yml @@ -37,7 +37,7 @@ jobs: - uses: stellar/quickstart@main with: tag: nightly - protocol_version: 27 + protocol_version: 28 - uses: actions/checkout@v7 - uses: stellar/actions/rust-cache@main diff --git a/.github/workflows/rpc-tests.yml b/.github/workflows/rpc-tests.yml index 59fc6e6fd1..71cbd3d37c 100644 --- a/.github/workflows/rpc-tests.yml +++ b/.github/workflows/rpc-tests.yml @@ -33,8 +33,8 @@ jobs: steps: - uses: stellar/quickstart@main with: - tag: latest - protocol_version: 27 + tag: nightly + protocol_version: 28 - uses: actions/checkout@v7 - uses: stellar/actions/rust-cache@main - run: rustup update From b15c5ad73b98f502ded7ad8aff1df6bde0aa5bd8 Mon Sep 17 00:00:00 2001 From: Leigh <351529+leighmcculloch@users.noreply.github.com> Date: Mon, 10 Aug 2026 06:43:23 +0000 Subject: [PATCH 5/5] match __check_auth method form in bindings test --- cmd/crates/soroban-test/tests/it/integration/bindings.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/cmd/crates/soroban-test/tests/it/integration/bindings.rs b/cmd/crates/soroban-test/tests/it/integration/bindings.rs index ef421ad122..4da22a64e7 100644 --- a/cmd/crates/soroban-test/tests/it/integration/bindings.rs +++ b/cmd/crates/soroban-test/tests/it/integration/bindings.rs @@ -68,9 +68,13 @@ async fn invoke_test_bindings_context_failure() { assert!(index_ts_path.exists(), "src/index.ts file does not exist"); let content = std::fs::read_to_string(&index_ts_path).expect("Failed to read index.ts file"); + // Match the method form `__check_auth:` specifically: the name also + // appears in doc text of SDK auth types that legitimately remain in the + // spec now that the sdk emits entries for all types used at the + // contract boundary. assert!( - !content.contains("__check_auth"), - "Test failed: `__check_auth` found in src/index.ts" + !content.contains("__check_auth:"), + "Test failed: `__check_auth:` method found in src/index.ts" ); // check enum message + doc working properly