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 diff --git a/Cargo.lock b/Cargo.lock index a63546cca6..ae63bc80bf 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=997dd6dbe9c0ce01a3c4cc940c392e9debab12dd#997dd6dbe9c0ce01a3c4cc940c392e9debab12dd" 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=997dd6dbe9c0ce01a3c4cc940c392e9debab12dd#997dd6dbe9c0ce01a3c4cc940c392e9debab12dd" 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=997dd6dbe9c0ce01a3c4cc940c392e9debab12dd#997dd6dbe9c0ce01a3c4cc940c392e9debab12dd" 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=997dd6dbe9c0ce01a3c4cc940c392e9debab12dd#997dd6dbe9c0ce01a3c4cc940c392e9debab12dd" 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=997dd6dbe9c0ce01a3c4cc940c392e9debab12dd#997dd6dbe9c0ce01a3c4cc940c392e9debab12dd" 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=997dd6dbe9c0ce01a3c4cc940c392e9debab12dd#997dd6dbe9c0ce01a3c4cc940c392e9debab12dd" 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=997dd6dbe9c0ce01a3c4cc940c392e9debab12dd#997dd6dbe9c0ce01a3c4cc940c392e9debab12dd" 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=997dd6dbe9c0ce01a3c4cc940c392e9debab12dd#997dd6dbe9c0ce01a3c4cc940c392e9debab12dd" 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=2e565108988d9181c26477815e708ade929e67e0#2e565108988d9181c26477815e708ade929e67e0" 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..e5fc66b44b 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 = "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 = "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" } + [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/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 a12d5821b8..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 { @@ -137,17 +168,9 @@ impl Spec { ), 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() + 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_) { @@ -170,11 +193,31 @@ impl Spec { } } + 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, + }, + }; + 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| { @@ -185,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 }) @@ -192,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 @@ -206,7 +279,7 @@ impl Spec { /// pub fn find_functions(&self) -> Result, Error> { Ok(self - .0 + .entries .as_deref() .ok_or(Error::MissingSpec)? .iter() @@ -236,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() @@ -288,6 +361,14 @@ impl Spec { { Ok(Value::String(s.to_owned())) } + ScType::UdtV2(udt) + if matches!( + self.find_udt_v2(udt)?, + ScSpecEntry::UdtUnionV0(_) | ScSpecEntry::UdtStructV0(_) + ) => + { + Ok(Value::String(s.to_owned())) + } _ => Err(Error::Serde(e)), }, |val| match t { @@ -364,7 +445,10 @@ 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(self.find(&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)?, @@ -372,9 +456,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, entry: &ScSpecEntry, value: &Value) -> Result { + match (entry, value) { (ScSpecEntry::UdtStructV0(strukt), Value::Object(map)) => { if strukt .fields @@ -649,9 +732,7 @@ 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, udt: &ScSpecEntry, sc_obj: &ScVal) -> Result { Ok(match (sc_obj, udt) { (ScVal::Map(Some(map)), ScSpecEntry::UdtStructV0(strukt)) => serde_json::Value::Object( strukt @@ -757,7 +838,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(self.find(&name.to_utf8_string_lossy())?, sc_obj)?, + (sc_obj @ (ScVal::Vec(_) | ScVal::Map(_) | ScVal::U32(_)), ScType::UdtV2(udt)) => { + 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)?, @@ -997,6 +1081,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 +1183,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)) } @@ -1214,32 +1311,38 @@ impl Spec { } 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, - } + 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_v2(udt).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(_) + | ScSpecEntry::V2(_) => None, + } + } + fn arg_value_udt(&self, strukt: &ScSpecUdtStructV0, depth: usize) -> Option { let inner = strukt .fields @@ -1272,8 +1375,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 +1493,14 @@ impl Spec { Some(format!("\"{res}\"")) } ScType::Udt(ScSpecTypeUdt { name }) => { - self.example_udts(depth, 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()) } @@ -1394,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() @@ -1440,7 +1560,8 @@ impl Spec { Some( ScSpecEntry::FunctionV0(_) | ScSpecEntry::UdtErrorEnumV0(_) - | ScSpecEntry::EventV0(_), + | ScSpecEntry::EventV0(_) + | ScSpecEntry::V2(_), ) | None => None, }; @@ -2435,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), @@ -2453,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), @@ -2474,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, @@ -2496,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())), @@ -2511,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(), )); @@ -2522,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 62ff7456d2..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, }; @@ -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, @@ -263,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/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..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/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 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..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)?; @@ -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.name.to_utf8_string_lossy()) + .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,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, 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(), spec, config)?; } _ => {} } @@ -776,13 +790,12 @@ fn resolve_aliases_in_json( fn resolve_aliases_in_udt( value: &mut serde_json::Value, - udt: &stellar_xdr::ScSpecTypeUdt, + name: &str, 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(name) else { return Ok(false); }; match entry { @@ -968,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 @@ -1076,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(), @@ -1086,7 +1099,7 @@ mod tests { })] .try_into() .unwrap(), - })])); + })]); let expected_type = ScSpecTypeDef::Udt(ScSpecTypeUdt { name: union_name }); let config = crate::config::Args::default(); @@ -1124,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(), @@ -1141,7 +1154,7 @@ mod tests { ] .try_into() .unwrap(), - })])); + })]); let expected_type = ScSpecTypeDef::Udt(ScSpecTypeUdt { name: union_name }); let config = crate::config::Args::default(); @@ -1199,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) } @@ -1215,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"); @@ -1243,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]); @@ -1268,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]); @@ -1325,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(), @@ -1338,7 +1351,7 @@ mod tests { })] .try_into() .unwrap(), - })])); + })]); let ty = ScSpecTypeDef::Udt(ScSpecTypeUdt { name: union_name }); let config = crate::config::Args::default(); @@ -1363,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(), @@ -1374,7 +1387,7 @@ mod tests { })] .try_into() .unwrap(), - })])); + })]); let ty = ScSpecTypeDef::Udt(ScSpecTypeUdt { name: union_name }); let config = crate::config::Args::default(); @@ -1399,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; @@ -1434,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); @@ -1470,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(), @@ -1486,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/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..6f252ba792 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=2e565108988d9181c26477815e708ade929e67e0", + # The rs-soroban-sdk PR stamping user-defined type spec entries with ids + "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", ] [sources.allow-org]