diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index a2cd48d..c324190 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -48,3 +48,5 @@ jobs: run: just clippy-test - name: Unit Tests run: just test + - name: Fixture tests (import_contract! + register_import!) + run: just test-register-import diff --git a/CLAUDE.md b/CLAUDE.md index f2d1116..72307f6 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -42,7 +42,7 @@ Note: the `justfile` still carries some recipes from the monorepo. Prefer the `c | `stellar-registry-cli` | The `stellar registry` CLI: `publish`, `deploy`, `download`, `install`/`create-alias`, `upgrade`, `register-contract`, and `account` (`account register`, `account preauthorize`, ...) | | `stellar-registry-build` | Library for interacting with the registry at build time | | `stellar-registry-macro` | Macro crate defining procedural macros `import_contract!`, `import_contract_client!`, and `import_asset`. Proc-macro crates are special and need to only export proc-macros. | -| `stellar-registry` | Re-exports the `import_contract!`, `import_contract_client!`, and `import_asset!` macros from `stellar-registry-macro`. Might export more behavior later. (published to crates.io; dev-dependency of `stellar-registry/contracts`) | +| `stellar-registry` | Re-exports the `import_contract!`, `import_contract_client!`, and `import_asset!` macros from `stellar-registry-macro`. Its `testutils` feature adds `testutils::register_import!` for testing contracts that use `import_contract!`. Might export more behavior later. (published to crates.io; dev-dependency of `stellar-registry/contracts`) | | `stellar-registry-name` | Defines standard name parsing/formatting used by `stellar-registry-macro` and `stellar-registry-cli` | | `stellar-registry-test` | Unpublished testing tools used throughout this monorepo as a dev-dependency | @@ -57,6 +57,8 @@ Note: the `justfile` still carries some recipes from the monorepo. Prefer the `c - Unit tests run without external dependencies. - Integration tests require a local Stellar RPC (Docker `stellar/quickstart`) and the `integration-tests` feature flag. +- `just test-register-import` runs the `hello_caller` fixture, which is excluded from the workspace because `import_contract!` needs seeded cache files to compile. +- `just test-register-import-testnet` runs the `token_caller` fixture against testnet; it needs network access, so CI doesn't run it. - Command tests use `stellar_registry_test::RegistryTest`, defined in local crate `crates/stellar-registry-test` ## Cross-repo dependencies diff --git a/Cargo.lock b/Cargo.lock index d48cc70..4b92f91 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4542,6 +4542,7 @@ dependencies = [ "quote", "sha2 0.11.1", "soroban-sdk", + "soroban-spec", "stellar-build", "stellar-registry-name", "stellar-strkey 0.0.18", diff --git a/Cargo.toml b/Cargo.toml index 810c29f..e9f34f9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,6 +2,11 @@ resolver = "2" default-members = ["crates/*"] members = ["crates/*", "crates/stellar-registry-test/fixtures/contracts/*"] +# Needs seeded `import_contract!` cache files to compile; see its Cargo.toml. +exclude = [ + "crates/stellar-registry-test/fixtures/contracts/hello_caller", + "crates/stellar-registry-test/fixtures/contracts/token_caller", +] [workspace.package] repository = "https://github.com/stellar-registry/cli" @@ -21,6 +26,7 @@ stellar-cli = { version = "28.1.0", package = "soroban-cli", default-features = soroban-rpc = { package = "stellar-rpc-client", version = "28.0.0" } soroban-sdk = { version = "28.0.0" } soroban-spec-tools = "28.1.0" +soroban-spec = "28.0.0" admin-sep = "0.28.1" stellar-xdr = "=30.0.0" diff --git a/crates/stellar-registry-macro/Cargo.toml b/crates/stellar-registry-macro/Cargo.toml index 3a691a4..6d344e1 100644 --- a/crates/stellar-registry-macro/Cargo.toml +++ b/crates/stellar-registry-macro/Cargo.toml @@ -19,6 +19,7 @@ stellar-build = { workspace = true } stellar-registry-name = { workspace = true } stellar-strkey = { workspace = true } stellar-xdr = { workspace = true } +soroban-spec = { workspace = true } [dev-dependencies] sha2 = { workspace = true } diff --git a/crates/stellar-registry-macro/README.md b/crates/stellar-registry-macro/README.md index 22ce223..b7ad408 100644 --- a/crates/stellar-registry-macro/README.md +++ b/crates/stellar-registry-macro/README.md @@ -1,6 +1,6 @@ # stellar-registry-macro -The [macro crate] defining `import_contract!`, `import_contract_client!`, and `import_asset!` which get re-exported by the [stellar-registry] crate. +The [macro crate] defining `import_contract!`, `import_contract_client!`, `import_asset!`, and the test helper `register_import!`, which get re-exported by the [stellar-registry] crate. [macro crate]: https://www.reddit.com/r/rust/comments/tuxawv/why_do_procedural_macros_have_to_be_defined_in_a/ [stellar-registry]: https://crates.io/crates/stellar-registry diff --git a/crates/stellar-registry-macro/src/contract.rs b/crates/stellar-registry-macro/src/contract.rs index d1f13a5..3145aef 100644 --- a/crates/stellar-registry-macro/src/contract.rs +++ b/crates/stellar-registry-macro/src/contract.rs @@ -12,6 +12,8 @@ use syn::{ use stellar_registry_name::Prefixed; +use stellar_xdr::{Limited, Limits, ReadXdr, ScSpecEntry, ScSpecTypeDef}; + use crate::asset; use crate::util::{Name, explorer_url, manifest, mod_ident, network_name, track_network_env}; @@ -31,6 +33,97 @@ impl Parse for Input { } } +/// `args = { name: value, .. }`, or a positional tuple `args = (..)`. +enum Args { + Named { + brace: syn::token::Brace, + fields: Vec<(Ident, Expr)>, + }, + Positional(Expr), +} + +impl Parse for Args { + fn parse(input: ParseStream) -> syn::Result { + if !input.peek(syn::token::Brace) { + return Ok(Self::Positional(input.parse()?)); + } + let content; + let brace = syn::braced!(content in input); + let mut fields = Vec::new(); + while !content.is_empty() { + let name: Ident = content.parse()?; + // `{ admin }` is shorthand for `{ admin: admin }`, as in a struct literal. + let value = if content.peek(Token![:]) { + content.parse::()?; + content.parse()? + } else { + syn::parse_quote!(#name) + }; + fields.push((name, value)); + if content.is_empty() { + break; + } + content.parse::()?; + } + Ok(Self::Named { brace, fields }) + } +} + +/// `register_import!(env_expr, name [, args = (..)] [, contract = Mock])` — +/// `name` as in [`Input`]; the options may come in either order. +struct RegisterInput { + env: Expr, + name: Name, + /// Constructor arguments, checked against the imported Wasm's constructor. + args: Option, + /// A native contract to register instead of the imported Wasm. + contract: Option, +} + +impl Parse for RegisterInput { + fn parse(input: ParseStream) -> syn::Result { + let Input { env, name } = input.parse()?; + let (mut args, mut contract) = (None, None); + while !input.is_empty() { + input.parse::()?; + if input.is_empty() { + break; + } + let key: Ident = input.parse()?; + let given = match key.to_string().as_str() { + "args" => args.is_some(), + "contract" => contract.is_some(), + other => { + return Err(syn::Error::new( + key.span(), + format!( + "unknown option `{other}`: expected `args = (..)` or `contract = ..`" + ), + )); + } + }; + if given { + return Err(syn::Error::new( + key.span(), + format!("`{key}` given more than once"), + )); + } + input.parse::()?; + if key == "args" { + args = Some(input.parse()?); + } else { + contract = Some(input.parse()?); + } + } + Ok(Self { + env, + name, + args, + contract, + }) + } +} + /// Whether `raw` names an asset rather than a registered contract: exactly /// `native`/`xlm`, or a `CODE:ISSUER` literal. Colons are never valid in a /// registry name (`Prefixed` rejects them outright), so `raw.contains(':')` @@ -292,10 +385,26 @@ fn expand_asset_token_client(env: &Expr, asset_xdr: &[u8]) -> proc_macro2::Token } } -pub(crate) fn import_contract( - input: proc_macro::TokenStream, -) -> syn::Result { - let Input { env, name } = syn::parse(input)?; +/// What a name resolved to at build time. +#[derive(Debug)] +enum Resolved { + /// A Wasm-backed contract: its deployed address and cached Wasm. + Wasm { + mod_ident: Ident, + wasm_path: PathBuf, + address: String, + }, + /// A registered name that points at a Stellar Asset Contract. There is + /// no Wasm. + Sac { address: String }, + /// Asset syntax: the SAC address is derived at runtime from this XDR. + Asset { xdr: Vec }, +} + +/// Resolve `name` for `macro_name!` — shared by `import_contract!` and +/// `register_import!` so a test registers at exactly the address (and with +/// exactly the Wasm) the contract under test was compiled against. +fn resolve(name: &Name, macro_name: &str) -> syn::Result { let span = name.span(); let err = |msg: String| syn::Error::new(span, msg); @@ -304,16 +413,16 @@ pub(crate) fn import_contract( let raw = name.raw(); if raw.contains('@') { return Err(err(format!( - "import_contract! does not take a version — a deployed contract has no version \ - (got {raw:?}). Use just the contract name, e.g. `import_contract!(env, our_dao)`." + "{macro_name}! does not take a version — a deployed contract has no version \ + (got {raw:?}). Use just the contract name, e.g. `{macro_name}!(env, our_dao)`." ))); } // Asset syntax (`xlm`, `native`, `"CODE:ISSUER"`) needs no registry // lookup or cache files: the SAC address is derived at runtime. if is_asset_syntax(&raw) { - let (asset_xdr, _code) = asset::asset_xdr(&raw).map_err(err)?; - return Ok(expand_asset_token_client(&env, &asset_xdr)); + let (xdr, _code) = asset::asset_xdr(&raw).map_err(err)?; + return Ok(Resolved::Asset { xdr }); } let contract: Prefixed = name.parse_as()?; @@ -349,7 +458,7 @@ pub(crate) fn import_contract( if cached_sac_address.is_some() && !wasm_is_stale(cached_sac_address.as_deref(), &address, no_registry) { - return Ok(expand_token_client(&env, &address)); + return Ok(Resolved::Sac { address }); } // 3. Ensure the deployed contract's wasm is on disk for `contractimport!`, @@ -371,19 +480,276 @@ pub(crate) fn import_contract( // doesn't repeat a fetch that can only fail the same way again. Err(e) if is_stellar_asset_fetch_error(&e) => { record_sac_marker(&sac_marker_path, &address); - return Ok(expand_token_client(&env, &address)); + return Ok(Resolved::Sac { address }); } Err(e) => return Err(err(e)), } } - // 4. Generate the client from that wasm and bind it to the address. - Ok(expand( - &env, - &mod_ident, - &wasm_path.to_string_lossy(), - &address, - )) + Ok(Resolved::Wasm { + mod_ident, + wasm_path, + address, + }) +} + +pub(crate) fn import_contract( + input: proc_macro::TokenStream, +) -> syn::Result { + let Input { env, name } = syn::parse(input)?; + Ok(match resolve(&name, "import_contract")? { + Resolved::Wasm { + mod_ident, + wasm_path, + address, + } => expand(&env, &mod_ident, &wasm_path.to_string_lossy(), &address), + Resolved::Sac { address } => expand_token_client(&env, &address), + Resolved::Asset { xdr } => expand_asset_token_client(&env, &xdr), + }) +} + +/// Emit a block expression registering a contract at the address +/// `import_contract!` baked in: the imported Wasm, or `contract` (a native +/// mock) when given. Evaluates to the `Address`. +/// The constructor's parameters (name and type), read from the contract spec +/// embedded in the Wasm; `None` if the contract has no constructor. +fn constructor_params(wasm: &[u8]) -> Result>, String> { + let err = + |e: &dyn std::fmt::Display| format!("could not read the contract spec from the Wasm: {e}"); + // Only the raw section comes from soroban-spec; it is parsed with this + // crate's stellar-xdr, which needn't match soroban-spec's. + let raw = soroban_spec::read::raw_from_wasm(wasm).map_err(|e| err(&e))?; + let mut reader = Limited::new(std::io::Cursor::new(raw), Limits::none()); + let entries = ScSpecEntry::read_xdr_iter(&mut reader) + .collect::, _>>() + .map_err(|e| err(&e))?; + Ok(entries.into_iter().find_map(|entry| match entry { + ScSpecEntry::FunctionV0(f) if f.name.to_utf8_string_lossy() == "__constructor" => Some( + f.inputs + .iter() + .map(|i| (i.name.to_utf8_string_lossy(), i.type_.clone())) + .collect(), + ), + _ => None, + })) +} + +/// A spec type as the Rust type a test would pass, for error messages. +fn type_name(t: &ScSpecTypeDef) -> String { + use ScSpecTypeDef as T; + match t { + T::Bool => "bool".into(), + T::U32 => "u32".into(), + T::I32 => "i32".into(), + T::U64 => "u64".into(), + T::I64 => "i64".into(), + T::U128 => "u128".into(), + T::I128 => "i128".into(), + T::Address => "Address".into(), + T::MuxedAddress => "MuxedAddress".into(), + T::String => "String".into(), + T::Symbol => "Symbol".into(), + T::Bytes => "Bytes".into(), + T::BytesN(b) => format!("BytesN<{}>", b.n), + T::Option(o) => format!("Option<{}>", type_name(&o.value_type)), + T::Vec(v) => format!("Vec<{}>", type_name(&v.element_type)), + T::Map(m) => format!( + "Map<{}, {}>", + type_name(&m.key_type), + type_name(&m.value_type) + ), + T::Udt(u) => u.name.to_utf8_string_lossy(), + other => other.name().to_string(), + } +} + +/// `args = { admin:
, fee: }` — a starting point to paste. +fn args_suggestion(params: &[(String, ScSpecTypeDef)]) -> String { + let fields: Vec = params + .iter() + .map(|(name, t)| format!("{name}: <{}>", type_name(t))) + .collect(); + format!("args = {{ {} }}", fields.join(", ")) +} + +/// Order named `args` by the constructor's parameters, reporting unknown, +/// repeated and missing names. +fn order_named_args<'a>( + fields: &'a [(Ident, Expr)], + params: &[(String, ScSpecTypeDef)], + brace_span: proc_macro2::Span, +) -> syn::Result> { + let expected = || args_suggestion(params); + for (i, (name, _)) in fields.iter().enumerate() { + if !params.iter().any(|(p, _)| name == p) { + return Err(syn::Error::new( + name.span(), + format!( + "the constructor has no parameter `{name}`; expected {}", + expected() + ), + )); + } + if fields[..i].iter().any(|(earlier, _)| earlier == name) { + return Err(syn::Error::new( + name.span(), + format!("`{name}` given more than once"), + )); + } + } + params + .iter() + .map(|(p, _)| { + fields + .iter() + .find(|(name, _)| name == p) + .map(|(_, value)| value) + .ok_or_else(|| { + syn::Error::new( + brace_span, + format!( + "missing constructor argument `{p}`; expected {}", + expected() + ), + ) + }) + }) + .collect() +} + +/// Constructor args for the imported Wasm, as a typed call to the +/// `Args::__constructor` that `contractimport!` generates, so rustc checks +/// each value against the constructor's parameter types. +fn wasm_args( + input: &RegisterInput, + mod_ident: &Ident, + params: Option<&[(String, ScSpecTypeDef)]>, +) -> syn::Result { + let name = input.name.raw(); + let values: Vec<&Expr> = match (&input.args, params) { + (None, None | Some([])) => return Ok(quote!(())), + (None, Some(params)) => { + return Err(syn::Error::new( + input.name.span(), + format!( + "`{name}`'s constructor takes arguments. Pass them with `args`:\n\n \ + register_import!(env, {name}, {})", + args_suggestion(params) + ), + )); + } + (Some(args), None) => { + let span = match args { + Args::Named { brace, .. } => brace.span.join(), + Args::Positional(e) => syn::spanned::Spanned::span(e), + }; + return Err(syn::Error::new( + span, + format!("`{name}` has no constructor, so it takes no `args`"), + )); + } + (Some(Args::Named { brace, fields }), Some(params)) => { + order_named_args(fields, params, brace.span.join())? + } + (Some(Args::Positional(Expr::Tuple(tuple))), Some(_)) => tuple.elems.iter().collect(), + (Some(Args::Positional(other)), Some(_)) => return Ok(quote!(#other)), + }; + // Spanned per value, so a type mismatch points at the value, not the macro. + let values = values + .iter() + .map(|v| quote::quote_spanned!(syn::spanned::Spanned::span(v)=> &(#v))); + Ok(quote!(#mod_ident::Args::__constructor(#(#values),*))) +} + +/// Emit a block expression registering a contract at the address +/// `import_contract!` baked in: the imported Wasm, or `contract` (a native +/// mock) when given. Evaluates to the `Address`. +fn expand_register( + input: &RegisterInput, + resolved: &Resolved, + params: Option<&[(String, ScSpecTypeDef)]>, +) -> syn::Result { + let (mod_ident, wasm_path, address) = match resolved { + Resolved::Wasm { + mod_ident, + wasm_path, + address, + } => (mod_ident, wasm_path, address), + Resolved::Sac { .. } | Resolved::Asset { .. } => { + return Err(syn::Error::new( + input.name.span(), + format!( + "register_import! does not support Stellar Asset Contracts yet, and `{}` \ + resolves to one. SAC support is coming; until then, register a test asset \ + yourself with `Env::register_stellar_asset_contract_v2`.", + input.name.raw() + ), + )); + } + }; + let env = &input.env; + let track = track_network_env(); + let (module, contract, args) = if let Some(contract) = &input.contract { + // A native mock has no spec to name its parameters by. + let args = match &input.args { + None => quote!(()), + Some(Args::Positional(args)) => quote!(#args), + Some(Args::Named { brace, .. }) => { + return Err(syn::Error::new( + brace.span.join(), + "named `args` need the imported Wasm's constructor; with `contract = ..`, \ + pass the mock's constructor arguments as a tuple, `args = (..)`", + )); + } + }; + (quote!(), quote!(#contract), args) + } else { + let args = wasm_args(input, mod_ident, params)?; + let wasm_path = wasm_path.to_string_lossy(); + ( + quote! { + #[allow(non_snake_case)] + mod #mod_ident { + #![allow(clippy::ref_option, clippy::too_many_arguments)] + use ::soroban_sdk; + soroban_sdk::contractimport!(file = #wasm_path); + } + }, + quote!(#mod_ident::WASM), + args, + ) + }; + Ok(quote! { + { + #track + #module + let __env: &::soroban_sdk::Env = #env; + ::soroban_sdk::Env::register_at( + __env, + &::soroban_sdk::Address::from_str(__env, #address), + #contract, + #args, + ) + } + }) +} + +pub(crate) fn register_import( + input: proc_macro::TokenStream, +) -> syn::Result { + let input: RegisterInput = syn::parse(input)?; + let resolved = resolve(&input.name, "register_import")?; + // Only the imported Wasm's constructor is read; a mock has no spec. + let params = match (&resolved, &input.contract) { + (Resolved::Wasm { wasm_path, .. }, None) => { + let err = |e: String| syn::Error::new(input.name.span(), e); + let wasm = std::fs::read(wasm_path) + .map_err(|e| err(format!("could not read {}: {e}", wasm_path.display())))?; + constructor_params(&wasm).map_err(err)? + } + _ => None, + }; + expand_register(&input, &resolved, params.as_deref()) } #[cfg(test)] @@ -643,3 +1009,203 @@ mod codegen { assert!(err.contains("invalid issuer"), "{err}"); } } + +#[cfg(test)] +mod register { + use super::*; + use proc_macro2::Span; + use quote::quote; + use syn::{Ident, parse2}; + + const A: &str = "CBESJIMX7J53SWJGJ7WQ6QTLJI4S5LPPJNC2BNVD63GIKAYCDTDOO322"; + + fn wasm() -> Resolved { + Resolved::Wasm { + mod_ident: Ident::new("our_dao", Span::call_site()), + wasm_path: "/tmp/target/stellar/local/deployed/our_dao.wasm".into(), + address: A.to_string(), + } + } + + /// `__constructor(admin: Address, fee: u32)`. + fn params() -> Vec<(String, ScSpecTypeDef)> { + vec![ + ("admin".into(), ScSpecTypeDef::Address), + ("fee".into(), ScSpecTypeDef::U32), + ] + } + + fn expand( + tokens: proc_macro2::TokenStream, + resolved: &Resolved, + params: Option<&[(String, ScSpecTypeDef)]>, + ) -> syn::Result { + let input: RegisterInput = parse2(tokens).unwrap(); + expand_register(&input, resolved, params).map(|out| out.to_string()) + } + + fn expanded(tokens: proc_macro2::TokenStream, resolved: &Resolved) -> String { + expand(tokens, resolved, None).unwrap() + } + + fn with_params(tokens: proc_macro2::TokenStream) -> syn::Result { + expand(tokens, &wasm(), Some(¶ms())) + } + + fn parse_err(tokens: proc_macro2::TokenStream) -> String { + parse2::(tokens).err().unwrap().to_string() + } + + #[test] + fn parses_name_only() { + let input: RegisterInput = parse2(quote!(&env, "unverified/our-dao")).unwrap(); + assert_eq!(input.name.raw(), "unverified/our-dao"); + assert!(input.args.is_none()); + assert!(input.contract.is_none()); + } + + #[test] + fn parses_options_in_either_order_with_trailing_comma() { + for tokens in [ + quote!(&env, our_dao, args = (&admin, 7u32), contract = MockDao,), + quote!(&env, our_dao, contract = MockDao, args = (&admin, 7u32)), + ] { + let input: RegisterInput = parse2(tokens).unwrap(); + assert!(input.args.is_some()); + assert!(input.contract.is_some()); + } + } + + #[test] + fn rejects_unknown_and_repeated_options() { + let msg = parse_err(quote!(&env, our_dao, wasm = X)); + assert!(msg.contains("unknown option `wasm`"), "{msg}"); + let msg = parse_err(quote!(&env, our_dao, args = (), args = ())); + assert!(msg.contains("`args` given more than once"), "{msg}"); + // A positional third argument is not accepted. + assert!(parse2::(quote!(&env, our_dao, (admin,))).is_err()); + } + + #[test] + fn registers_the_cached_wasm_at_the_baked_address() { + let out = expanded(quote!(&env, our_dao), &wasm()); + assert!(out.contains("Env :: register_at"), "{out}"); + assert!(out.contains("contractimport !"), "{out}"); + assert!(out.contains("our_dao.wasm"), "{out}"); + assert!( + out.contains("our_dao :: WASM , () ,"), + "no constructor: {out}" + ); + assert!(out.contains(A), "bakes the resolved id: {out}"); + } + + #[test] + fn named_args_are_ordered_by_the_constructor_and_typed() { + // Given out of order, and `admin` in shorthand. + let out = with_params(quote!(&env, our_dao, args = { fee: 7u32, admin })).unwrap(); + assert!( + out.contains("our_dao :: Args :: __constructor (& (admin) , & (7u32))"), + "{out}" + ); + } + + #[test] + fn positional_tuple_args_are_typed_too() { + let out = with_params(quote!(&env, our_dao, args = (&admin, 7u32))).unwrap(); + assert!( + out.contains("our_dao :: Args :: __constructor (& (& admin) , & (7u32))"), + "{out}" + ); + } + + #[test] + fn missing_args_suggest_the_constructor_parameters() { + let msg = with_params(quote!(&env, our_dao)).unwrap_err().to_string(); + assert!(msg.contains("constructor takes arguments"), "{msg}"); + assert!( + msg.contains("register_import!(env, our_dao, args = { admin:
, fee: })"), + "{msg}" + ); + } + + #[test] + fn named_args_report_unknown_repeated_and_missing_names() { + let msg = with_params(quote!(&env, our_dao, args = { owner: a, fee: 1u32 })) + .unwrap_err() + .to_string(); + assert!(msg.contains("no parameter `owner`"), "{msg}"); + let msg = with_params(quote!(&env, our_dao, args = { admin, admin, fee: 1u32 })) + .unwrap_err() + .to_string(); + assert!(msg.contains("`admin` given more than once"), "{msg}"); + let msg = with_params(quote!(&env, our_dao, args = { admin })) + .unwrap_err() + .to_string(); + assert!(msg.contains("missing constructor argument `fee`"), "{msg}"); + } + + #[test] + fn args_without_a_constructor_are_rejected() { + let msg = expand(quote!(&env, our_dao, args = { admin }), &wasm(), None) + .unwrap_err() + .to_string(); + assert!(msg.contains("has no constructor"), "{msg}"); + } + + #[test] + fn named_args_are_rejected_for_a_mock() { + let msg = expand( + quote!(&env, our_dao, contract = MockDao, args = { admin }), + &wasm(), + None, + ) + .unwrap_err() + .to_string(); + assert!(msg.contains("as a tuple"), "{msg}"); + let out = expanded( + quote!(&env, our_dao, contract = MockDao, args = (&admin,)), + &wasm(), + ); + assert!(out.contains("MockDao , (& admin ,)"), "{out}"); + } + + #[test] + fn spec_types_are_named_as_rust_types() { + use stellar_xdr::{ScSpecTypeBytesN, ScSpecTypeVec}; + assert_eq!(type_name(&ScSpecTypeDef::I128), "i128"); + assert_eq!( + type_name(&ScSpecTypeDef::BytesN(ScSpecTypeBytesN { n: 32 })), + "BytesN<32>" + ); + assert_eq!( + type_name(&ScSpecTypeDef::Vec(Box::new(ScSpecTypeVec { + element_type: Box::new(ScSpecTypeDef::Address), + }))), + "Vec
" + ); + } + + #[test] + fn contract_option_replaces_the_wasm() { + let out = expanded(quote!(&env, our_dao, contract = mocks::MockDao), &wasm()); + assert!(out.contains("mocks :: MockDao"), "{out}"); + assert!(!out.contains("contractimport"), "{out}"); + assert!(out.contains(A), "still at the baked address: {out}"); + } + + #[test] + fn stellar_asset_contracts_are_a_clear_compile_error() { + let sac = Resolved::Sac { + address: A.to_string(), + }; + for tokens in [ + quote!(&env, xlm), + quote!(&env, "circle/usdc", contract = MockToken), + ] { + let input: RegisterInput = parse2(tokens).unwrap(); + let msg = expand_register(&input, &sac, None).unwrap_err().to_string(); + assert!(msg.contains("Stellar Asset Contracts"), "{msg}"); + assert!(msg.contains("SAC support is coming"), "{msg}"); + } + } +} diff --git a/crates/stellar-registry-macro/src/lib.rs b/crates/stellar-registry-macro/src/lib.rs index 9d157c8..3a08ac3 100644 --- a/crates/stellar-registry-macro/src/lib.rs +++ b/crates/stellar-registry-macro/src/lib.rs @@ -1,7 +1,9 @@ //! Proc macros for the Stellar Registry: import deployed contracts //! (`import_contract!`), published wasms (`import_contract_client!`), and -//! Stellar assets (`import_asset!`) as type-safe soroban clients. Registry -//! names resolve at build time; asset addresses are derived at runtime. +//! Stellar assets (`import_asset!`) as type-safe soroban clients, plus +//! `register_import!` for testing contracts that use `import_contract!`. +//! Registry names resolve at build time; asset addresses are derived at +//! runtime. extern crate proc_macro; use proc_macro::TokenStream; @@ -64,11 +66,65 @@ use util::ProcMacroWrapper as _; /// resolved address turns out to be a built-in asset contract rather than a /// wasm-backed one, the same `token::TokenClient` is bound to it instead of /// generating types from a (nonexistent) fetched wasm. +/// +/// # Testing +/// +/// Nothing lives at the baked address in a test `Env`; use +/// [`register_import!`] to register the imported contract (or a mock) there. #[proc_macro] pub fn import_contract(input: TokenStream) -> TokenStream { contract::import_contract(input).to_token_stream() } +/// Register an [`import_contract!`]-ed contract in a test `Env`, at the +/// address `import_contract!` baked in. Evaluates to that `Address`. +/// +/// `import_contract!(env, our_dao)` compiles a real on-chain address into +/// your contract, but nothing lives at that address in a sandboxed test +/// `Env`. `register_import!` puts the imported contract there, so the code +/// under test runs unchanged: +/// +/// ```ignore +/// use stellar_registry::testutils::register_import; +/// +/// let env = Env::default(); +/// // The imported contract's own Wasm, as cached by `import_contract!`: +/// register_import!(&env, our_dao); +/// // ...with constructor arguments, by name: +/// register_import!(&env, our_dao, args = { admin, quorum: 7u32 }); +/// // ...or a native mock in place of the Wasm: +/// register_import!(&env, our_dao, contract = MockDao); +/// ``` +/// +/// - `env` is a `&Env`; the name is written exactly as in `import_contract!` +/// and resolved by the same code, so both macros always agree on the +/// address and Wasm (same cache files, same `STELLAR_NO_REGISTRY` +/// handling, same flagged-contract check). +/// - `args = { name: value, .. }` passes constructor arguments by parameter +/// name, read from the imported Wasm's constructor; `{ admin }` is +/// shorthand for `{ admin: admin }`. Values are borrowed, not moved, and +/// type-checked against the constructor. Leaving out `args` when the +/// constructor takes parameters is a compile error that lists them. A +/// positional tuple, `args = (..)`, also works. +/// - `contract = ` registers a native `#[contract]` type instead of the +/// Wasm, for when you want to control what the dependency returns. Its +/// constructor arguments are a tuple, `args = (..)`, since a mock has no +/// spec to name them by. +/// +/// Exported as `stellar_registry::testutils::register_import!`, behind +/// `stellar-registry`'s `testutils` feature. The expansion calls +/// `Env::register_at`, so `soroban-sdk`'s `testutils` feature must be enabled +/// too. +/// +/// Stellar Asset Contracts — `xlm`/`native`, `"CODE:ISSUER"`, or a registered +/// name that resolves to a SAC — are not supported yet and fail to compile; +/// register a test asset with `Env::register_stellar_asset_contract_v2` +/// instead. +#[proc_macro] +pub fn register_import(input: TokenStream) -> TokenStream { + contract::register_import(input).to_token_stream() +} + /// Generate a contract client from a published wasm — from your workspace's /// `target` directory if present, otherwise downloaded from the registry. /// diff --git a/crates/stellar-registry-test/fixtures/contracts/hello_caller/Cargo.lock b/crates/stellar-registry-test/fixtures/contracts/hello_caller/Cargo.lock new file mode 100644 index 0000000..30dec20 --- /dev/null +++ b/crates/stellar-registry-test/fixtures/contracts/hello_caller/Cargo.lock @@ -0,0 +1,2252 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "ahash" +version = "0.8.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" +dependencies = [ + "cfg-if", + "once_cell", + "version_check", + "zerocopy", +] + +[[package]] +name = "allocator-api2" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" + +[[package]] +name = "android_system_properties" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae221649c9976a6f6c56ae1facf410f3ddb33cc661c4b7b61020a912d4237fbc" +dependencies = [ + "libc", +] + +[[package]] +name = "arbitrary" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d5a26814d8dcb93b0e5a0ff3c6d80a8843bafb21b39e8e18a6f05471870e110" +dependencies = [ + "derive_arbitrary", +] + +[[package]] +name = "ark-bls12-381" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3df4dcc01ff89867cd86b0da835f23c3f02738353aaee7dde7495af71363b8d5" +dependencies = [ + "ark-ec", + "ark-ff", + "ark-serialize", + "ark-std", +] + +[[package]] +name = "ark-bn254" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d69eab57e8d2663efa5c63135b2af4f396d66424f88954c21104125ab6b3e6bc" +dependencies = [ + "ark-ec", + "ark-ff", + "ark-std", +] + +[[package]] +name = "ark-ec" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43d68f2d516162846c1238e755a7c4d131b892b70cc70c471a8e3ca3ed818fce" +dependencies = [ + "ahash", + "ark-ff", + "ark-poly", + "ark-serialize", + "ark-std", + "educe", + "fnv", + "hashbrown 0.15.5", + "itertools", + "num-bigint", + "num-integer", + "num-traits", + "zeroize", +] + +[[package]] +name = "ark-ff" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a177aba0ed1e0fbb62aa9f6d0502e9b46dad8c2eab04c14258a1212d2557ea70" +dependencies = [ + "ark-ff-asm", + "ark-ff-macros", + "ark-serialize", + "ark-std", + "arrayvec", + "digest 0.10.7", + "educe", + "itertools", + "num-bigint", + "num-traits", + "paste", + "zeroize", +] + +[[package]] +name = "ark-ff-asm" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62945a2f7e6de02a31fe400aa489f0e0f5b2502e69f95f853adb82a96c7a6b60" +dependencies = [ + "quote", + "syn 2.0.119", +] + +[[package]] +name = "ark-ff-macros" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09be120733ee33f7693ceaa202ca41accd5653b779563608f1234f78ae07c4b3" +dependencies = [ + "num-bigint", + "num-traits", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "ark-poly" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "579305839da207f02b89cd1679e50e67b4331e2f9294a57693e5051b7703fe27" +dependencies = [ + "ahash", + "ark-ff", + "ark-serialize", + "ark-std", + "educe", + "fnv", + "hashbrown 0.15.5", +] + +[[package]] +name = "ark-serialize" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f4d068aaf107ebcd7dfb52bc748f8030e0fc930ac8e360146ca54c1203088f7" +dependencies = [ + "ark-serialize-derive", + "ark-std", + "arrayvec", + "digest 0.10.7", + "num-bigint", +] + +[[package]] +name = "ark-serialize-derive" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "213888f660fddcca0d257e88e54ac05bca01885f258ccdf695bafd77031bb69d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "ark-std" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "246a225cc6131e9ee4f24619af0f19d67761fff15d7ccc22e42b80846e69449a" +dependencies = [ + "num-traits", + "rand", +] + +[[package]] +name = "arrayvec" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3fb67a6e08acf24fdeccbac2cb6ac4305825bd1f117462e0e6f2f193345ad56" + +[[package]] +name = "autocfg" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" + +[[package]] +name = "base16ct" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "base64" +version = "0.23.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac07cdecf99051d9a5238b80f35af32cdeba5b336e55d957b318b50137e18da5" + +[[package]] +name = "base64ct" +version = "1.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "block-buffer" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2f6c7dbe95a6ed67ad9f18e57daf93a2f034c524b99fd2b76d18fdfeb6660aa" +dependencies = [ + "hybrid-array", +] + +[[package]] +name = "bs58" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf88ba1141d185c399bee5288d850d63b8369520c1eafc32a0430b5b6c287bf4" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "bumpalo" +version = "3.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "bytes-lit" +version = "0.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b04f2b1d34cb428043f14aa4c853d14294532e8bbde3b6a3bc2faaaae31a1dd" +dependencies = [ + "num-bigint", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "camino" +version = "1.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbbad30e4b4c14a39e3cc8aed085a12a327257c316619c93581e017bc52be591" +dependencies = [ + "serde_core", +] + +[[package]] +name = "cargo-platform" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e35af189006b9c0f00a064685c727031e3ed2d8020f7ba284d78cc2671bd36ea" +dependencies = [ + "serde", +] + +[[package]] +name = "cargo_metadata" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d886547e41f740c616ae73108f6eb70afe6d940c7bc697cb30f13daec073037" +dependencies = [ + "camino", + "cargo-platform", + "semver", + "serde", + "serde_json", + "thiserror 1.0.69", +] + +[[package]] +name = "cc" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6651c9ed80effdc7db0ff72512157f901af5e3549e341e24b1dd4887d836d838" +dependencies = [ + "find-msvc-tools", + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e7648175b45a9a48536d676f68d918270699102aa8dab5496df06904c914600" + +[[package]] +name = "cfg_eval" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45565fc9416b9896014f5732ac776f810ee53a66730c17e4020c3ec064a8f88f" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "chrono" +version = "0.4.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1aa79e62e7697b8e29b513a68abacf485adcd1fe8284a4316c5ae868e6633327" +dependencies = [ + "iana-time-zone", + "num-traits", + "serde", + "windows-link", +] + +[[package]] +name = "const-oid" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "cpufeatures" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ca28b0ae3115b884660db4118d803791fd6756b6e88f39c0f3f7859060d7566" +dependencies = [ + "libc", +] + +[[package]] +name = "crate-git-revision" +version = "0.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c521bf1f43d31ed2f73441775ed31935d77901cb3451e44b38a1c1612fcbaf98" +dependencies = [ + "serde", + "serde_derive", + "serde_json", +] + +[[package]] +name = "crate-git-revision" +version = "0.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54851b5b3f24621804b1cded2820975623c205e3055d2d44031cdb1237339ac8" +dependencies = [ + "serde", + "serde_derive", + "serde_json", +] + +[[package]] +name = "crypto-bigint" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" +dependencies = [ + "generic-array", + "rand_core", + "subtle", + "zeroize", +] + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "crypto-common" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce6e4c961d6cd6c9a86db418387425e8bdeaf05b3c8bc1411e6dca4c252f1453" +dependencies = [ + "hybrid-array", +] + +[[package]] +name = "ctor" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67773048316103656a637612c4a62477603b777d91d9c62ff2290f9cde178fdb" +dependencies = [ + "ctor-proc-macro", + "dtor", +] + +[[package]] +name = "ctor-proc-macro" +version = "0.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2931af7e13dc045d8e9d26afccc6fa115d64e115c9c84b1166288b46f6782c2" + +[[package]] +name = "curve25519-dalek" +version = "4.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be" +dependencies = [ + "cfg-if", + "cpufeatures 0.2.17", + "curve25519-dalek-derive", + "digest 0.10.7", + "fiat-crypto 0.2.9", + "rustc_version", + "subtle", + "zeroize", +] + +[[package]] +name = "curve25519-dalek" +version = "5.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5eed333089e2e1c1ac8c6c0398e5e2497b4c9926ca6d0365ed1e099afa5bc23" +dependencies = [ + "cfg-if", + "cpufeatures 0.3.1", + "curve25519-dalek-derive", + "digest 0.11.3", + "fiat-crypto 0.3.0", + "rustc_version", + "subtle", +] + +[[package]] +name = "curve25519-dalek-derive" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "darling" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee" +dependencies = [ + "darling_core 0.20.11", + "darling_macro 0.20.11", +] + +[[package]] +name = "darling" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed17f5901b6630b993ca003def43f2f8ef4014fc13b047b57aad617ff32bc2ec" +dependencies = [ + "darling_core 0.24.1", + "darling_macro 0.24.1", +] + +[[package]] +name = "darling_core" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d00b9596d185e565c2207a0b01f8bd1a135483d02d9b7b0a54b11da8d53412e" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 2.0.119", +] + +[[package]] +name = "darling_core" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6837e2cf7485aaae18f86181d2f0e9a7ed297a025e220aeabf63fdebd3a2ddff" +dependencies = [ + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 3.0.9", +] + +[[package]] +name = "darling_macro" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" +dependencies = [ + "darling_core 0.20.11", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "darling_macro" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ac7135c3ef02b2f7833bbeb1be5ba7f966dcde8a87c6b87f65a778d71a02785" +dependencies = [ + "darling_core 0.24.1", + "quote", + "syn 3.0.9", +] + +[[package]] +name = "data-encoding" +version = "2.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4583a4551df46e2792f82ceeac45e850d2e2d5debba0b91f102385cda5b11f06" + +[[package]] +name = "defmt" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2953bfe4f93bbd20cc71198842756f77d161884c99ebbabc41d80231ded88d1" +dependencies = [ + "bitflags", + "defmt-macros", +] + +[[package]] +name = "defmt-macros" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bad9c72e7ca2137e0dc3813245a0d282fd6daad32fd800af018306a9169b5fe8" +dependencies = [ + "defmt-parser", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "defmt-parser" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10d60334b3b2e7c9d91ef8150abfb6fa4c1c39ebbcf4a81c2e346aad939fee3e" +dependencies = [ + "thiserror 2.0.21", +] + +[[package]] +name = "der" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" +dependencies = [ + "const-oid", + "zeroize", +] + +[[package]] +name = "deranged" +version = "0.5.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e9de72ce2ad1f90dc62fa25f0f430ef85eb4b0d8fa0be4f30373bc40a21d28e" +dependencies = [ + "serde_core", +] + +[[package]] +name = "derive_arbitrary" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67e77553c4162a157adbf834ebae5b415acbecbeafc7a74b0e886657506a7611" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer 0.10.4", + "const-oid", + "crypto-common 0.1.6", + "subtle", +] + +[[package]] +name = "digest" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1dd6dbb5841937940781866fa1281a1ff7bd3bf827091440879f9994983d5c2" +dependencies = [ + "block-buffer 0.12.1", + "crypto-common 0.2.2", +] + +[[package]] +name = "downcast-rs" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2" + +[[package]] +name = "dtor" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "404d02eeb088a82cfd873006cb713fe411306c7d182c344905e101fb1167d301" +dependencies = [ + "dtor-proc-macro", +] + +[[package]] +name = "dtor-proc-macro" +version = "0.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f678cf4a922c215c63e0de95eb1ff08a958a81d47e485cf9da1e27bf6305cfa5" + +[[package]] +name = "dyn-clone" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" + +[[package]] +name = "ecdsa" +version = "0.16.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" +dependencies = [ + "der", + "digest 0.10.7", + "elliptic-curve", + "rfc6979", + "signature", +] + +[[package]] +name = "ed25519" +version = "2.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53" +dependencies = [ + "pkcs8", + "signature", +] + +[[package]] +name = "ed25519-dalek" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70e796c081cee67dc755e1a36a0a172b897fab85fc3f6bc48307991f64e4eca9" +dependencies = [ + "curve25519-dalek 4.1.3", + "ed25519", + "rand_core", + "serde", + "sha2", + "subtle", + "zeroize", +] + +[[package]] +name = "educe" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d7bc049e1bd8cdeb31b68bbd586a9464ecf9f3944af3958a7a9d0f8b9799417" +dependencies = [ + "enum-ordinalize", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "either" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e9c71c2167ca323c882b99918929403426e2373ea17242ff5653e0d5e1058be" + +[[package]] +name = "elliptic-curve" +version = "0.13.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" +dependencies = [ + "base16ct", + "crypto-bigint", + "digest 0.10.7", + "ff", + "generic-array", + "group", + "rand_core", + "sec1", + "subtle", + "zeroize", +] + +[[package]] +name = "enum-ordinalize" +version = "4.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89dd01549b09589510cf0647475075d12071456586d70f5c75c98ae2a5537677" +dependencies = [ + "enum-ordinalize-derive", +] + +[[package]] +name = "enum-ordinalize-derive" +version = "4.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a65863d15a4ce2888bd2f0f543cc963d3879c3a022c8ee43f6141d479a3ac815" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.9", +] + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "escape-bytes" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bfcf67fea2815c2fc3b90873fae90957be12ff417335dfadc7f52927feb03b2" + +[[package]] +name = "ethnum" +version = "1.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40404c3f5f511ec4da6fe866ddf6a717c309fdbb69fbbad7b0f3edab8f2e835f" + +[[package]] +name = "ff" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0b50bfb653653f9ca9095b427bed08ab8d75a137839d9ad64eb11810d5b6393" +dependencies = [ + "rand_core", + "subtle", +] + +[[package]] +name = "fiat-crypto" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" + +[[package]] +name = "fiat-crypto" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64cd1e32ddd350061ae6edb1b082d7c54915b5c672c389143b9a63403a109f24" + +[[package]] +name = "find-msvc-tools" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aedcfb3409746eddb02b9e19ebda1c3394f759a152e48ee875a0844d1b955484" + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "futures-core" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92d699e522242e69e3003b94ecc1f960f3a5e015aa7c5d7486e65ad01dd94f5e" + +[[package]] +name = "futures-task" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd417de3d1d015fc3bfd2b1ea46dfc7bab72ef86f1cc7cc9c78e728b34a6d1fd" + +[[package]] +name = "futures-util" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d50a92467f8ba5dd6e3ee5d4bd04d73ab2e4e1c44474a0674821dfce14b79bc" +dependencies = [ + "futures-core", + "futures-task", + "pin-project-lite", + "slab", +] + +[[package]] +name = "generic-array" +version = "0.14.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bb6743198531e02858aeaea5398fcc883e71851fcbcb5a2f773e2fb6cb1edf2" +dependencies = [ + "typenum", + "version_check", + "zeroize", +] + +[[package]] +name = "getrandom" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "wasi", + "wasm-bindgen", +] + +[[package]] +name = "group" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" +dependencies = [ + "ff", + "rand_core", + "subtle", +] + +[[package]] +name = "hash32" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47d60b12902ba28e2730cd37e95b8c9223af2808df9e902d4df49588d1470606" +dependencies = [ + "byteorder", +] + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + +[[package]] +name = "hashbrown" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +dependencies = [ + "allocator-api2", +] + +[[package]] +name = "hashbrown" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" + +[[package]] +name = "heapless" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bfb9eb618601c89945a70e254898da93b13be0388091d42117462b265bb3fad" +dependencies = [ + "hash32", + "stable_deref_trait", +] + +[[package]] +name = "heapless" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25ba4bd83f9415b58b4ed8dc5714c76e626a105be4646c02630ad730ad3b5aa4" +dependencies = [ + "hash32", + "stable_deref_trait", + "zeroize", +] + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "hello_caller" +version = "0.0.0" +dependencies = [ + "soroban-sdk", + "stellar-registry", +] + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +dependencies = [ + "serde", +] + +[[package]] +name = "hex-literal" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fe2267d4ed49bc07b63801559be28c718ea06c4738b7a03c94df7386d2cde46" + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest 0.10.7", +] + +[[package]] +name = "hybrid-array" +version = "0.4.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27f864f10dfb56725ce5ce5472bc52252c8f93a4ab86327122cebf62c5f59a17" +dependencies = [ + "typenum", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.65" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "log", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + +[[package]] +name = "indexmap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown 0.12.3", + "serde", +] + +[[package]] +name = "indexmap" +version = "2.14.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc4e190f5d26ca7051642629da2c52fc03bde85a03197c99408dcd291734c855" +dependencies = [ + "equivalent", + "hashbrown 0.17.1", + "serde", + "serde_core", +] + +[[package]] +name = "indexmap-nostd" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e04e2fd2b8188ea827b32ef11de88377086d690286ab35747ef7f9bf3ccb590" + +[[package]] +name = "itertools" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" + +[[package]] +name = "jiff" +version = "0.2.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2b005715dcbeb0089a3c0dab99f2ff1cc3b2525323552703d648585d342a383" +dependencies = [ + "defmt", + "jiff-core", + "jiff-static", + "jiff-tzdb-platform", + "log", + "portable-atomic", + "portable-atomic-util", + "serde_core", + "windows-link", +] + +[[package]] +name = "jiff-core" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e52fe76043ccecc9005d2305ebaadf7d7fc0cc89ca6baa10a94d6bc68c7128c" +dependencies = [ + "defmt", + "log", +] + +[[package]] +name = "jiff-static" +version = "0.2.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2cc9817253cf7c7ee4684451bd327e88d6f3658014e54a29198625590650695c" +dependencies = [ + "jiff-core", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "jiff-tzdb" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa8377070c6bae868759445e5a77f66d84f0b72f3a054bfb00e6d038b8282da7" + +[[package]] +name = "jiff-tzdb-platform" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "875a5a69ac2bab1a891711cf5eccbec1ce0341ea805560dcd90b7a2e925132e8" +dependencies = [ + "jiff-tzdb", +] + +[[package]] +name = "js-sys" +version = "0.3.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7883d941dae510fb2d978fc3fe018c71c9e2892fd38854de3e8b92c2e5ad9cc5" +dependencies = [ + "cfg-if", + "futures-util", + "wasm-bindgen", +] + +[[package]] +name = "k256" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6e3919bbaa2945715f0bb6d3934a173d1e9a59ac23767fbaaef277265a7411b" +dependencies = [ + "cfg-if", + "ecdsa", + "elliptic-curve", + "sha2", +] + +[[package]] +name = "keccak" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb26cec98cce3a3d96cbb7bced3c4b16e3d13f27ec56dbd62cbc8f39cfb9d653" +dependencies = [ + "cpufeatures 0.2.17", +] + +[[package]] +name = "libc" +version = "0.2.190" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce5d3ddc6d3fa000eb1536d85e147bfe31aacaba692ed6a876f95cb7c855be78" + +[[package]] +name = "libm" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" + +[[package]] +name = "log" +version = "0.4.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9f8bd3e56ce4dfc153cf470fffbfa98c7620958b312ca5c3a4b8d5181fd13c6" + +[[package]] +name = "macro-string" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b27834086c65ec3f9387b096d66e99f221cf081c2b738042aa252bcd41204e3" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "memchr" +version = "2.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98" + +[[package]] +name = "num-bigint" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c89e69e7e0f03bea5ef08013795c25018e101932225a656383bd384495ecc367" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-conv" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "521739c6d2bac4aa25192232afe6841231376b2b26d4d9fae5ecf8ca5772e441" + +[[package]] +name = "num-derive" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "num-integer" +version = "0.1.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ce2d95d4b3734dc35aa2f45e1aa22cd416814592a4f9d9205e11affd5b8e10b" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "once_cell" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" + +[[package]] +name = "p256" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9863ad85fa8f4460f9c48cb909d38a0d689dba1f6f6988a5e3e0d31071bcd4b" +dependencies = [ + "ecdsa", + "elliptic-curve", + "primeorder", + "sha2", +] + +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + +[[package]] +name = "pin-project-lite" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" + +[[package]] +name = "pkcs8" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +dependencies = [ + "der", + "spki", +] + +[[package]] +name = "portable-atomic" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05c8b63e8d9609db387f0324918f81d68fe27748f084ef092fb35954d0539a85" + +[[package]] +name = "portable-atomic-util" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10ab3eb7f3becc3a1cbc4f2c6f20267996cfc1a6467a873763411b136a122715" +dependencies = [ + "portable-atomic", +] + +[[package]] +name = "powerfmt" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a6394b9e965e73d0a289ee54f589087e2c676aedf60885baf52c76b771e4958" + +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "prettyplease" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" +dependencies = [ + "proc-macro2", + "syn 2.0.119", +] + +[[package]] +name = "primeorder" +version = "0.13.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "353e1ca18966c16d9deb1c69278edbc5f194139612772bd9537af60ac231e1e6" +dependencies = [ + "elliptic-curve", +] + +[[package]] +name = "proc-macro2" +version = "1.0.107" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e058c7de0b26af77780c769414d6257830bb240f3c38477dbc2c16e5f54d6d4c" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "ref-cast" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e440fb4e4b4147295338efb76001ab9e4efc0e5839df2c47fc5ac2381d365c3" +dependencies = [ + "ref-cast-impl", +] + +[[package]] +name = "ref-cast-impl" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92ecd8964f8453721699a1ed72037b0db49ce2f5a5138486ee89bed6f67cdf3a" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.9", +] + +[[package]] +name = "rfc6979" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" +dependencies = [ + "hmac", + "subtle", +] + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + +[[package]] +name = "rustversion" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f" + +[[package]] +name = "schemars" +version = "0.8.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fbf2ae1b8bc8e02df939598064d22402220cd5bbcca1c76f7d6a310974d5615" +dependencies = [ + "dyn-clone", + "serde", + "serde_json", +] + +[[package]] +name = "schemars" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd191f9397d57d581cddd31014772520aa448f65ef991055d7f61582c65165f" +dependencies = [ + "dyn-clone", + "ref-cast", + "serde", + "serde_json", +] + +[[package]] +name = "schemars" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab508826f74a77ca9d5aba6ff19b522583ee3eaf28a19384ff3d0e5835fadf6e" +dependencies = [ + "dyn-clone", + "ref-cast", + "serde", + "serde_json", +] + +[[package]] +name = "sec1" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" +dependencies = [ + "base16ct", + "der", + "generic-array", + "subtle", + "zeroize", +] + +[[package]] +name = "semver" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" +dependencies = [ + "serde", + "serde_core", +] + +[[package]] +name = "serde" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.9", +] + +[[package]] +name = "serde_json" +version = "1.0.154" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7e9cc8b1b85264074fbcc02a88680c4096b1e47df8f739dceb03bf482f04bd6" +dependencies = [ + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[package]] +name = "serde_with" +version = "3.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df9adc193c780ef8f159aee8b61e2d5801aaa555e6eb0947fe45530ec506296f" +dependencies = [ + "base64 0.23.1", + "bs58", + "chrono", + "hex", + "indexmap 1.9.3", + "indexmap 2.14.2", + "jiff", + "schemars 0.8.22", + "schemars 0.9.0", + "schemars 1.2.3", + "serde_core", + "serde_json", + "serde_with_macros", + "time", +] + +[[package]] +name = "serde_with_macros" +version = "3.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e17bbc68e28663bbbb90df47e058aa7eda4fb445b89fe70457bb94fbccf6e49" +dependencies = [ + "darling 0.24.1", + "proc-macro2", + "quote", + "syn 3.0.9", +] + +[[package]] +name = "sha2" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +dependencies = [ + "cfg-if", + "cpufeatures 0.2.17", + "digest 0.10.7", +] + +[[package]] +name = "sha3" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77fd7028345d415a4034cf8777cd4f8ab1851274233b45f84e3d955502d93874" +dependencies = [ + "digest 0.10.7", + "keccak", +] + +[[package]] +name = "shlex" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" + +[[package]] +name = "signature" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" +dependencies = [ + "digest 0.10.7", + "rand_core", +] + +[[package]] +name = "slab" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" + +[[package]] +name = "smallvec" +version = "1.16.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b3dc8af474f516a851ff4bd12db780f948b9250ad37211e4eec0bccea54e01b" + +[[package]] +name = "soroban-builtin-sdk-macros" +version = "28.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86ffd329211a50add3965a8f4a8f70e24fc5c8268c945566ab963029b12dce40" +dependencies = [ + "itertools", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "soroban-env-common" +version = "28.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "610bb12ee937a56c2b629f8eb16efb7e47607cba4e79ab0c24132b9a53dd19a0" +dependencies = [ + "arbitrary", + "crate-git-revision 0.0.9", + "ethnum", + "num-derive", + "num-traits", + "serde", + "soroban-env-macros", + "soroban-wasmi", + "static_assertions", + "stellar-xdr 28.0.0", + "wasmparser", +] + +[[package]] +name = "soroban-env-guest" +version = "28.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d383176b27bb286f718ab0ff5713bd9b01132089151266359bd9b20f38684cf" +dependencies = [ + "soroban-env-common", + "static_assertions", +] + +[[package]] +name = "soroban-env-host" +version = "28.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36d7e5920c200939164cc58038224e2d90d7e2fabbfa505d6e2129d648dce7e2" +dependencies = [ + "ark-bls12-381", + "ark-bn254", + "ark-ec", + "ark-ff", + "ark-serialize", + "curve25519-dalek 5.0.0", + "ecdsa", + "ed25519-dalek", + "elliptic-curve", + "generic-array", + "getrandom", + "hex-literal", + "hmac", + "k256", + "num-derive", + "num-integer", + "num-traits", + "p256", + "rand", + "rand_chacha", + "sec1", + "sha2", + "sha3", + "soroban-builtin-sdk-macros", + "soroban-env-common", + "soroban-wasmi", + "static_assertions", + "stellar-strkey 0.0.13", + "wasmparser", +] + +[[package]] +name = "soroban-env-macros" +version = "28.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "077600bb175ea54592c1ac5983b8fb34ec9ab7a65f298af11d687b61e9d646a4" +dependencies = [ + "itertools", + "proc-macro2", + "quote", + "serde", + "serde_json", + "stellar-xdr 28.0.0", + "syn 2.0.119", +] + +[[package]] +name = "soroban-ledger-snapshot" +version = "28.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5caeb0a4c8af6d04dc1dfbf96199e0d50ae086c7d54c9001abe78bb891fc44f" +dependencies = [ + "serde", + "serde_json", + "serde_with", + "soroban-env-common", + "soroban-env-host", + "thiserror 1.0.69", +] + +[[package]] +name = "soroban-sdk" +version = "28.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4946477e75667215a25b652b0febb446485686c944d364c117c903e3fb98bab" +dependencies = [ + "arbitrary", + "bytes-lit", + "crate-git-revision 0.0.9", + "ctor", + "derive_arbitrary", + "ed25519-dalek", + "rand", + "rustc_version", + "serde", + "serde_json", + "soroban-env-guest", + "soroban-env-host", + "soroban-ledger-snapshot", + "soroban-sdk-macros", + "stellar-strkey 0.0.16", + "visibility", +] + +[[package]] +name = "soroban-sdk-macros" +version = "28.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "168f2d2c3fd4da57584c71e0a8315a8e656486495bc53e18c93b8ec4db085438" +dependencies = [ + "darling 0.20.11", + "heck", + "itertools", + "macro-string", + "proc-macro2", + "quote", + "sha2", + "soroban-env-common", + "soroban-spec", + "soroban-spec-rust", + "stellar-xdr 28.0.0", + "syn 2.0.119", +] + +[[package]] +name = "soroban-spec" +version = "28.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f48c8e183fc140ccd89e1fe1663cc605fb18641967c0bb591f64887599fde59" +dependencies = [ + "base64 0.22.1", + "sha2", + "stellar-xdr 28.0.0", + "thiserror 1.0.69", + "wasmparser", +] + +[[package]] +name = "soroban-spec-rust" +version = "28.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40cd0021887fd047e9f63cea36da35b202881bc0e0dbcc067a146137ca46f192" +dependencies = [ + "prettyplease", + "proc-macro2", + "quote", + "sha2", + "soroban-spec", + "stellar-xdr 28.0.0", + "syn 2.0.119", + "thiserror 1.0.69", +] + +[[package]] +name = "soroban-wasmi" +version = "0.31.1-soroban.20.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "710403de32d0e0c35375518cb995d4fc056d0d48966f2e56ea471b8cb8fc9719" +dependencies = [ + "smallvec", + "spin", + "wasmi_arena", + "wasmi_core", + "wasmparser-nostd", +] + +[[package]] +name = "spin" +version = "0.9.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3763264f6b73151db08c50ff20d7d8a0b8796e021cdea7ceedad07b80155fa0e" + +[[package]] +name = "spki" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" +dependencies = [ + "base64ct", + "der", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "stellar-build" +version = "0.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97fd5e3be902a9658fb9f642d4eef7837ab1d51e52dde9be1d9b218326499d97" +dependencies = [ + "cargo_metadata", + "sha2", + "thiserror 2.0.21", + "topological-sort", +] + +[[package]] +name = "stellar-registry" +version = "0.1.2" +dependencies = [ + "stellar-registry-macro", +] + +[[package]] +name = "stellar-registry-macro" +version = "0.1.2" +dependencies = [ + "proc-macro2", + "quote", + "soroban-spec", + "stellar-build", + "stellar-registry-name", + "stellar-strkey 0.0.18", + "stellar-xdr 30.0.0", + "syn 3.0.9", +] + +[[package]] +name = "stellar-registry-name" +version = "0.1.0" +dependencies = [ + "semver", + "thiserror 2.0.21", +] + +[[package]] +name = "stellar-strkey" +version = "0.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee1832fb50c651ad10f734aaf5d31ca5acdfb197a6ecda64d93fcdb8885af913" +dependencies = [ + "crate-git-revision 0.0.6", + "data-encoding", +] + +[[package]] +name = "stellar-strkey" +version = "0.0.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "084afcb0d458c3d5d5baa2d294b18f881e62cc258ef539d8fdf68be7dbe45520" +dependencies = [ + "crate-git-revision 0.0.6", + "data-encoding", + "heapless 0.8.0", +] + +[[package]] +name = "stellar-strkey" +version = "0.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f34ff61c0ca6f1c2b4169e8d1633417bd9225f58b6175e4e317204565d16277" +dependencies = [ + "crate-git-revision 0.0.9", + "data-encoding", + "heapless 0.9.3", + "zeroize", +] + +[[package]] +name = "stellar-strkey" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5038ed2bf4e11842210d118f65791d8ca4747622daddd222338b3d06b6051bc1" +dependencies = [ + "crate-git-revision 0.0.9", + "data-encoding", + "heapless 0.9.3", + "zeroize", +] + +[[package]] +name = "stellar-xdr" +version = "28.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f93d09ff8b9f919b084f664003c4c546ac66a76affd5429460dbe29f4b326f8e" +dependencies = [ + "arbitrary", + "base64 0.22.1", + "cfg_eval", + "crate-git-revision 0.0.9", + "escape-bytes", + "ethnum", + "hex", + "serde", + "serde_with", + "sha2", + "stellar-strkey 0.0.13", +] + +[[package]] +name = "stellar-xdr" +version = "30.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56f2fb2008b42fc746c0f6b46e375600068dd91f07748e9f1737c6341a2255f2" +dependencies = [ + "crate-git-revision 0.0.9", + "escape-bytes", + "ethnum", + "hex", + "sha2", + "stellar-strkey 1.0.0", +] + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[package]] +name = "syn" +version = "2.0.119" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "3.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d78c8dee4c7bf0e14673097256fed6142ce9d3b85a408189d07482442145823b" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl 1.0.69", +] + +[[package]] +name = "thiserror" +version = "2.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09e52cb86a36cede5cb101bf8908837b3e4c6e5e59fe7fd85c23fb56200d189e" +dependencies = [ + "thiserror-impl 2.0.21", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe5197923287db20a58125f0bc85c062f7f2c892de97b18c356f9efb14b28524" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.9", +] + +[[package]] +name = "time" +version = "0.3.55" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdb87b95ec50ddfa440816d227a17b2ccbdda963a316a727fda0fc4334f7d134" +dependencies = [ + "deranged", + "num-conv", + "powerfmt", + "serde_core", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1c906769ad99c88eaa54e728060edef082f8e358ff32030cb7c7d315e81109" + +[[package]] +name = "time-macros" +version = "0.2.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e689342a48d2ea927c87ea50cabf8594854bf940e9310208848d680d668ed85" +dependencies = [ + "num-conv", + "time-core", +] + +[[package]] +name = "tinyvec" +version = "1.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd3ca314f692efd6c868f8408f53fe444634a845f96c028b97d35f6a1f79f0ee" + +[[package]] +name = "topological-sort" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea68304e134ecd095ac6c3574494fc62b909f416c4fca77e440530221e549d3d" + +[[package]] +name = "typenum" +version = "1.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20" + +[[package]] +name = "unicode-ident" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2c754d6c33795a1c324727428e5a7dedb5b06195f9890bdbcba760d3e246563" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "visibility" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d674d135b4a8c1d7e813e2f8d1c9a58308aee4a680323066025e53132218bd91" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "wasm-bindgen" +version = "0.2.129" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9bb54f33acc68fd454578d9820b0bde1a1a3d17aa17bb7b6595806d02886d409" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.129" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e29d0c35b16e224a7eeb5cd2d25e3e1968fbd65604117b44d3b789d00ee8535" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.129" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f501a8bc3719dba86ef8ae4728879c08001bea749eb1333ac5b91e040e2a6b7" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn 3.0.9", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.129" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23f0c9c52aa7cd7d77769a4cfe2a9adb1b331f489a41d912ce14513d5ab995c6" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "wasmi_arena" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "104a7f73be44570cac297b3035d76b169d6599637631cf37a1703326a0727073" + +[[package]] +name = "wasmi_core" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcf1a7db34bff95b85c261002720c00c3a6168256dcb93041d3fa2054d19856a" +dependencies = [ + "downcast-rs", + "libm", + "num-traits", + "paste", +] + +[[package]] +name = "wasmparser" +version = "0.116.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a58e28b80dd8340cb07b8242ae654756161f6fc8d0038123d679b7b99964fa50" +dependencies = [ + "indexmap 2.14.2", + "semver", +] + +[[package]] +name = "wasmparser-nostd" +version = "0.100.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5a015fe95f3504a94bb1462c717aae75253e39b9dd6c3fb1062c934535c64aa" +dependencies = [ + "indexmap-nostd", +] + +[[package]] +name = "windows-core" +version = "0.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-implement" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "windows-interface" +version = "0.59.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-result" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" +dependencies = [ + "windows-link", +] + +[[package]] +name = "zerocopy" +version = "0.8.62" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86502bf56ac7c77571a32e2647bb2a15894565e981fb2a48d7bde2d91c965a9d" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.62" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5457206954b06561e2608c7e19cf58b1926586d999c246eebe4502f7e2039d1a" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "zeroize" +version = "1.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13084392c5e4bc371903e2935a5eaeed24905a7511356b883835e18a78f6879" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c50655cbb0fe3fc43170059e702f1ce5e19b84cec58dc87b037a09935c2f328" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "zmij" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b" diff --git a/crates/stellar-registry-test/fixtures/contracts/hello_caller/Cargo.toml b/crates/stellar-registry-test/fixtures/contracts/hello_caller/Cargo.toml new file mode 100644 index 0000000..daa2837 --- /dev/null +++ b/crates/stellar-registry-test/fixtures/contracts/hello_caller/Cargo.toml @@ -0,0 +1,27 @@ +[package] +name = "hello_caller" +version = "0.0.0" +edition = "2024" +publish = false + +# Demonstrates testing a contract that uses `import_contract!` with +# `stellar_registry::testutils::register_import!` — see src/lib.rs. +# +# Excluded from the repo workspace: `import_contract!(env, hello_v1)` needs +# hello_v1's id and Wasm in this crate's `target/stellar/local/deployed/` +# before it compiles, so a plain `cargo build --workspace` could not build it. +# `just test-register-import` seeds those files and runs the tests offline. +[workspace] + +[lib] +crate-type = ["cdylib"] +doctest = false + +[dependencies] +# Same requirement as the root workspace's soroban-sdk. +soroban-sdk = "28.0.0" +stellar-registry = { path = "../../../../stellar-registry" } + +[dev-dependencies] +soroban-sdk = { version = "28.0.0", features = ["testutils"] } +stellar-registry = { path = "../../../../stellar-registry", features = ["testutils"] } diff --git a/crates/stellar-registry-test/fixtures/contracts/hello_caller/src/lib.rs b/crates/stellar-registry-test/fixtures/contracts/hello_caller/src/lib.rs new file mode 100644 index 0000000..b4e977f --- /dev/null +++ b/crates/stellar-registry-test/fixtures/contracts/hello_caller/src/lib.rs @@ -0,0 +1,74 @@ +#![no_std] +//! Demonstrates unit-testing a contract that calls another contract through +//! `stellar_registry::import_contract!`. +//! +//! `import_contract!(env, hello_v1)` bakes `hello_v1`'s deployed address into +//! this contract at build time, and nothing lives at that address in a +//! sandboxed test `Env`. `register_import!` resolves the same name the same +//! way and registers a contract there — `hello_v1`'s own Wasm, or a native +//! mock. +use soroban_sdk::{Env, String, contract, contractimpl}; + +#[contract] +pub struct Contract; + +#[contractimpl] +impl Contract { + /// Greet `to` by way of the deployed `hello_v1` contract. + pub fn greet(env: &Env, to: String) -> String { + stellar_registry::import_contract!(env, hello_v1).hello(&to) + } +} + +#[cfg(test)] +mod test { + use super::*; + use soroban_sdk::{Address, testutils::Address as _}; + use stellar_registry::testutils::register_import; + + #[contract] + struct MockHello; + + #[contractimpl] + impl MockHello { + pub fn hello(env: &Env, to: String) -> String { + let _ = to; + String::from_str(env, "mocked") + } + } + + fn greet(env: &Env, to: &str) -> String { + let client = ContractClient::new(env, &env.register(Contract, ())); + client.greet(&String::from_str(env, to)) + } + + #[test] + fn greets_through_the_imported_wasm() { + let env = Env::default(); + let admin = Address::generate(&env); + // `hello_v1`'s constructor takes an admin. + register_import!(&env, hello_v1, args = { admin }); + + assert_eq!(greet(&env, "world"), String::from_str(&env, "world")); + } + + #[test] + fn greets_through_a_native_mock() { + let env = Env::default(); + register_import!(&env, hello_v1, contract = MockHello); + + assert_eq!(greet(&env, "world"), String::from_str(&env, "mocked")); + } + + #[test] + fn registers_at_the_same_address_either_way() { + let env = Env::default(); + let admin = Address::generate(&env); + let wasm = register_import!(&env, hello_v1, args = { admin }); + let mock = register_import!(&env, hello_v1, contract = MockHello); + + assert_eq!(wasm, mock); + // Registering again replaced the Wasm with the mock. + assert_eq!(greet(&env, "world"), String::from_str(&env, "mocked")); + } +} diff --git a/crates/stellar-registry-test/fixtures/contracts/token_caller/.gitignore b/crates/stellar-registry-test/fixtures/contracts/token_caller/.gitignore new file mode 100644 index 0000000..03314f7 --- /dev/null +++ b/crates/stellar-registry-test/fixtures/contracts/token_caller/.gitignore @@ -0,0 +1 @@ +Cargo.lock diff --git a/crates/stellar-registry-test/fixtures/contracts/token_caller/Cargo.toml b/crates/stellar-registry-test/fixtures/contracts/token_caller/Cargo.toml new file mode 100644 index 0000000..70dfc2b --- /dev/null +++ b/crates/stellar-registry-test/fixtures/contracts/token_caller/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "token_caller" +version = "0.0.0" +edition = "2024" +publish = false + +# Imports `unverified/my-token`, which only resolves on testnet, so it can't +# be a workspace member. Run with `just test-register-import-testnet`. +[workspace] + +[lib] +crate-type = ["cdylib"] +doctest = false + +[dependencies] +soroban-sdk = "28.0.0" +stellar-registry = { path = "../../../../stellar-registry" } + +[dev-dependencies] +soroban-sdk = { version = "28.0.0", features = ["testutils"] } +stellar-registry = { path = "../../../../stellar-registry", features = ["testutils"] } diff --git a/crates/stellar-registry-test/fixtures/contracts/token_caller/src/lib.rs b/crates/stellar-registry-test/fixtures/contracts/token_caller/src/lib.rs new file mode 100644 index 0000000..acf83ef --- /dev/null +++ b/crates/stellar-registry-test/fixtures/contracts/token_caller/src/lib.rs @@ -0,0 +1,36 @@ +#![no_std] +//! Imports `unverified/my-token`, a token registered on testnet, so both +//! macros fetch its id and Wasm from testnet. +use soroban_sdk::{Env, String, contract, contractimpl}; + +#[contract] +pub struct Contract; + +#[contractimpl] +impl Contract { + pub fn token_symbol(env: &Env) -> String { + stellar_registry::import_contract!(env, "unverified/my-token").symbol() + } +} + +#[cfg(test)] +mod test { + use super::*; + use soroban_sdk::{Address, testutils::Address as _}; + use stellar_registry::testutils::register_import; + + #[test] + fn reads_the_imported_tokens_symbol() { + let env = Env::default(); + register_import!(&env, "unverified/my-token", args = { + name: String::from_str(&env, "Test Token"), + symbol: String::from_str(&env, "TT"), + decimals: 7u32, + owner: Address::generate(&env), + initial_supply: 1_000i128, + }); + + let client = ContractClient::new(&env, &env.register(Contract, ())); + assert_eq!(client.token_symbol(), String::from_str(&env, "TT")); + } +} diff --git a/crates/stellar-registry/Cargo.toml b/crates/stellar-registry/Cargo.toml index f3083d0..6b6a68a 100644 --- a/crates/stellar-registry/Cargo.toml +++ b/crates/stellar-registry/Cargo.toml @@ -12,5 +12,9 @@ crate-type = ["rlib"] # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +[features] +# Test helpers (`testutils::register_import!`); enable from dev-dependencies. +testutils = [] + [dependencies] stellar-registry-macro = { workspace = true } diff --git a/crates/stellar-registry/README.md b/crates/stellar-registry/README.md index b1d74de..67b9c32 100644 --- a/crates/stellar-registry/README.md +++ b/crates/stellar-registry/README.md @@ -26,6 +26,61 @@ pub fn your_fn(env: &Env) { A registered name that itself points at a SAC (e.g. `"circle/usdc"`) works the same way, going through the registry to resolve the address and falling back to the same token client instead of trying (and failing) to fetch a wasm that doesn't exist for a built-in asset contract. +## Testing with `import_contract!` + +`import_contract!` bakes the contract's real on-chain address into your contract at build time. A unit test's sandboxed `Env` has nothing at that address, so calling through the import fails until you put a contract there. `register_import!` does that: it resolves the name exactly as `import_contract!` does and registers the imported contract at the same address, returning the `Address`. + +Enable the `testutils` feature for tests (alongside `soroban-sdk`'s own): + +```toml +[dependencies] +soroban-sdk = "27" +stellar-registry = "0.1" + +[dev-dependencies] +soroban-sdk = { version = "27", features = ["testutils"] } +stellar-registry = { version = "0.1", features = ["testutils"] } +``` + +Then, given a contract that imports `our_dao`: + +```rs +pub fn propose(env: &Env, title: String) -> u32 { + stellar_registry::import_contract!(env, our_dao).create_proposal(&title) +} +``` + +...its tests register `our_dao` before calling it. Your contract code stays the same in tests and on-chain, with no `#[cfg(test)]` switch: + +```rs +use stellar_registry::testutils::register_import; + +#[test] +fn proposes() { + let env = Env::default(); + // Runs the same Wasm `import_contract!` generated its client from. + register_import!(&env, our_dao); + + let client = ContractClient::new(&env, &env.register(Contract, ())); + assert_eq!(client.propose(&String::from_str(&env, "Ship it")), 1); +} +``` + +Two options, in either order: + +- `args = { .. }` passes constructor arguments by name: `register_import!(&env, our_dao, args = { admin, quorum: 7u32 })`. The names and types come from the imported contract's constructor, so a misspelled name or wrong type is a compile error, and leaving `args` out when the constructor needs some fails with the list to fill in, e.g. `args = { admin:
, quorum: }`. `{ admin }` is shorthand for `{ admin: admin }`; values are borrowed, not moved. A positional tuple, `args = (&admin, 7u32)`, also works. +- `contract = ..` registers a native contract in place of the Wasm, when you want to control what the dependency returns: `register_import!(&env, our_dao, contract = MockDao)`. `MockDao` is an ordinary `#[contract]` type with the functions your contract calls; pass its constructor arguments as a tuple. + +Running tests is an ordinary build, so there is nothing to build first. Both macros resolve the name at compile time on the network the build uses, and share the cached id and Wasm (`target/stellar//deployed/.id` and `.wasm`). Run tests on a network where the name is registered: + +```bash +STELLAR_NETWORK=testnet cargo test +``` + +For tests with no network access, such as in CI, set `STELLAR_NO_REGISTRY=1` and put those two files in place before building (from `stellar registry fetch-contract-id` and `stellar contract fetch`, or from copies you commit). The [`hello_caller` fixture](https://github.com/stellar-registry/cli/tree/main/crates/stellar-registry-test/fixtures/contracts/hello_caller) does this, run by `just test-register-import`. + +Stellar Asset Contracts (`xlm`, `"CODE:ISSUER"`, or a registered name that resolves to a SAC) are not supported by `register_import!` yet and give a compile error. Until they are, register a test asset with `Env::register_stellar_asset_contract_v2`. + # Import wasm with `import_contract_client!` Import a wasm (https://stellar.rgstry.xyz/wasms), which defines only behavior. You can optionally include a version, otherwise it fetches the latest. You need to instantiate with a contract ID. diff --git a/crates/stellar-registry/src/lib.rs b/crates/stellar-registry/src/lib.rs index 7d820c0..c19198f 100644 --- a/crates/stellar-registry/src/lib.rs +++ b/crates/stellar-registry/src/lib.rs @@ -3,3 +3,9 @@ //! existing smart contracts on Stellar. //! pub use stellar_registry_macro::{import_asset, import_contract, import_contract_client}; + +/// Helpers for unit-testing contracts that use [`import_contract!`]. +#[cfg(feature = "testutils")] +pub mod testutils { + pub use stellar_registry_macro::register_import; +} diff --git a/justfile b/justfile index bb74bb3..3fe32f8 100644 --- a/justfile +++ b/justfile @@ -49,6 +49,23 @@ _test-integration package filter ci="false": test-integration-registry ci="false": just _test-integration stellar-registry-cli 'test(/./)' {{ ci }} +# Test the hello_caller fixture (import_contract! + register_import!) offline, seeding the cache files import_contract! would otherwise fetch +test-register-import: + #!/usr/bin/env bash + set -euo pipefail + fixture=crates/stellar-registry-test/fixtures/contracts/hello_caller + deployed=$fixture/target/stellar/local/deployed + stellar contract build --profile contracts --package hello_v1 + mkdir -p $deployed + cp target/wasm32v1-none/contracts/hello_v1.wasm $deployed/hello_v1.wasm + # Any valid contract id works: nothing is looked up on a network. + echo CBESJIMX7J53SWJGJ7WQ6QTLJI4S5LPPJNC2BNVD63GIKAYCDTDOO322 > $deployed/hello_v1.id + STELLAR_NO_REGISTRY=1 STELLAR_NETWORK=local cargo test --manifest-path $fixture/Cargo.toml + +# Test the token_caller fixture against testnet +test-register-import-testnet: build + STELLAR_NETWORK=testnet cargo test --manifest-path crates/stellar-registry-test/fixtures/contracts/token_caller/Cargo.toml + clippy *args: cargo clippy --all {{ args }} \ -- -Dclippy::pedantic -Aclippy::must_use_candidate -Aclippy::missing_errors_doc -Aclippy::missing_panics_doc