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..1eebd8b 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -41,8 +41,8 @@ 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-macro` | Macro crate defining procedural macros `import_contract!`, `import_contract_client!`, `import_asset!`, and the test helper `register_import!`. 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`. 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,7 @@ 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. - 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 fff8a8f..2108fc8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4548,6 +4548,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 f666a24..35bc3a2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,6 +2,10 @@ 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", +] [workspace.package] repository = "https://github.com/stellar-registry/cli" @@ -21,6 +25,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-build/src/error.rs b/crates/stellar-registry-build/src/error.rs index f038b57..2b46ae2 100644 --- a/crates/stellar-registry-build/src/error.rs +++ b/crates/stellar-registry-build/src/error.rs @@ -25,4 +25,12 @@ pub enum Error { Rpc(#[from] soroban_rpc::Error), #[error(transparent)] Xdr(#[from] stellar_cli::xdr::Error), + #[error( + "no Stellar Registry is deployed on the local network (looked for one at \ + {id}{from_env}). Deploy the registry contract on local and set \ + STELLAR_REGISTRY_CONTRACT_ID to its id, or set up local development with Stellar \ + Scaffold.", + from_env = if *.from_env { " (from STELLAR_REGISTRY_CONTRACT_ID)" } else { "" } + )] + NoLocalRegistry { id: String, from_env: bool }, } diff --git a/crates/stellar-registry-build/src/registry.rs b/crates/stellar-registry-build/src/registry.rs index 216704c..d6b0ef6 100644 --- a/crates/stellar-registry-build/src/registry.rs +++ b/crates/stellar-registry-build/src/registry.rs @@ -1,4 +1,4 @@ -use stellar_cli::config; +use stellar_cli::config::{self, network::passphrase}; use crate::{ Error, @@ -18,6 +18,7 @@ impl Registry { pub async fn new(config: &config::Args, name: Option<&str>) -> Result { let contract = Self::verified(config)?; + contract.ensure_local_root_exists(config).await?; Ok(if let Some(name) = name { if let Ok(contract_id) = name.parse() { Registry(Contract::new(contract_id, config)) @@ -190,6 +191,29 @@ impl Registry { ) } + /// On a local network nothing is at the canonical root address unless + /// someone deployed a registry there, and every command would otherwise + /// fail with a bare "contract not found". Elsewhere the root exists. + async fn ensure_local_root_exists(&self, config: &config::Args) -> Result<(), Error> { + use stellar_cli::xdr; + if config.get_network()?.network_passphrase != passphrase::LOCAL { + return Ok(()); + } + let instance = self + .contract_data( + xdr::ScVal::LedgerKeyContractInstance, + xdr::ContractDataDurability::Persistent, + ) + .await?; + if instance.is_some() { + return Ok(()); + } + Err(Error::NoLocalRegistry { + id: format!("{}", self.0.id()), + from_env: std::env::var("STELLAR_REGISTRY_CONTRACT_ID").is_ok(), + }) + } + pub fn as_contract(&self) -> &Contract { &self.0 } diff --git a/crates/stellar-registry-cli/src/commands/fetch_contract_id.rs b/crates/stellar-registry-cli/src/commands/fetch_contract_id.rs index e8e0794..b3bd4d7 100644 --- a/crates/stellar-registry-cli/src/commands/fetch_contract_id.rs +++ b/crates/stellar-registry-cli/src/commands/fetch_contract_id.rs @@ -141,6 +141,28 @@ mod tests { assert!(!contract_id.to_string().is_empty()); } + #[tokio::test] + async fn explains_a_missing_local_registry() { + let registry = RegistryTest::new().await; + // A valid contract id with nothing deployed at it. + let missing = "CBESJIMX7J53SWJGJ7WQ6QTLJI4S5LPPJNC2BNVD63GIKAYCDTDOO322"; + let stderr = registry + .registry_cli("fetch-contract-id") + .env("STELLAR_REGISTRY_CONTRACT_ID", missing) + .arg("hello") + .assert() + .failure() + .get_output() + .stderr + .clone(); + let stderr = String::from_utf8_lossy(&stderr); + assert!( + stderr.contains("no Stellar Registry is deployed on the local network"), + "{stderr}" + ); + assert!(stderr.contains(missing), "{stderr}"); + } + #[tokio::test] async fn flagged() { let registry = RegistryTest::new().await; diff --git a/crates/stellar-registry-macro/Cargo.toml b/crates/stellar-registry-macro/Cargo.toml index 188704e..584a11b 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..7434d91 100644 --- a/crates/stellar-registry-macro/src/contract.rs +++ b/crates/stellar-registry-macro/src/contract.rs @@ -17,9 +17,9 @@ use crate::util::{Name, explorer_url, manifest, mod_ident, network_name, track_n /// `import_contract!(env_expr, name)` — `name` is a bare ident or a string /// literal (optionally channel-prefixed, e.g. `"unverified/our_dao"`). -struct Input { - env: Expr, - name: Name, +pub(crate) struct Input { + pub(crate) env: Expr, + pub(crate) name: Name, } impl Parse for Input { @@ -292,10 +292,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)] +pub(crate) 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. +pub(crate) fn resolve(name: &Name, macro_name: &str) -> syn::Result { let span = name.span(); let err = |msg: String| syn::Error::new(span, msg); @@ -304,16 +320,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 +365,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 +387,32 @@ 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), + }) } #[cfg(test)] diff --git a/crates/stellar-registry-macro/src/lib.rs b/crates/stellar-registry-macro/src/lib.rs index 9d157c8..46fd455 100644 --- a/crates/stellar-registry-macro/src/lib.rs +++ b/crates/stellar-registry-macro/src/lib.rs @@ -1,13 +1,16 @@ //! 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; mod asset; mod contract; mod contract_client; +mod register; mod util; use util::ProcMacroWrapper as _; @@ -64,11 +67,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. Values are +/// 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. A mock +/// can have its own constructor, so `args` may be given with `contract`, but +/// only as a tuple: named `args` are rejected, 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 { + register::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-macro/src/register.rs b/crates/stellar-registry-macro/src/register.rs new file mode 100644 index 0000000..14c979d --- /dev/null +++ b/crates/stellar-registry-macro/src/register.rs @@ -0,0 +1,545 @@ +//! `register_import!`: register an `import_contract!`-ed contract in a test +//! `Env` at the address `import_contract!` resolves to. +use quote::quote; +use syn::{ + Expr, Ident, Token, + parse::{Parse, ParseStream}, +}; + +use stellar_xdr::{Limited, Limits, ReadXdr, ScSpecEntry, ScSpecTypeDef}; + +use crate::contract::{Input, Resolved, resolve}; +use crate::util::{Name, track_network_env}; + +/// `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` must match the contract's `import_contract!` call, since both +/// resolve it to the same address. 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 = {{ .. }}`, `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, + }) + } +} + +/// 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)] +mod tests { + 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-test/fixtures/contracts/hello_caller/Cargo.lock b/crates/stellar-registry-test/fixtures/contracts/hello_caller/Cargo.lock new file mode 100644 index 0000000..a534417 --- /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.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "915a3455d5ef6013227e3ba69e7fd966c0830c4620f84c06b1dc684cae3a9465" +dependencies = [ + "cargo_metadata", + "sha2", + "thiserror 2.0.21", + "topological-sort", +] + +[[package]] +name = "stellar-registry" +version = "0.1.3" +dependencies = [ + "stellar-registry-macro", +] + +[[package]] +name = "stellar-registry-macro" +version = "0.1.3" +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/Cargo.toml b/crates/stellar-registry/Cargo.toml index c6d0395..a4d1644 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..884931c 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 = { workspace = true } +stellar-registry = { workspace = true } + +[dev-dependencies] +soroban-sdk = { workspace = true, features = ["testutils"] } +stellar-registry = { workspace = true, 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: }`. 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`](https://github.com/stellar-registry/cli/blob/main/justfile). + +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..f0017b6 100644 --- a/justfile +++ b/justfile @@ -49,6 +49,19 @@ _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 + clippy *args: cargo clippy --all {{ args }} \ -- -Dclippy::pedantic -Aclippy::must_use_candidate -Aclippy::missing_errors_doc -Aclippy::missing_panics_doc