feat: import_contract! macro + consolidate registry macros (stellar-scaffold/cli#419)#17
feat: import_contract! macro + consolidate registry macros (stellar-scaffold/cli#419)#17willemneal wants to merge 12 commits into
import_contract! macro + consolidate registry macros (stellar-scaffold/cli#419)#17Conversation
Addresses review on #17 ("stop slopping me bro"): the macro was built by analogy to import_contract_client! and conflated a contract with its wasm. - Drop @Version. A deployed contract has no version (only a wasm does); reject `@` with a clear compile_error!. - Stop delegating codegen to import_contract_client!(name), which resolves a wasm by name and wrongly assumes contract-name == wasm-name. Instead fetch the deployed contract's own on-chain wasm by address (`stellar contract fetch --id`) and inline `soroban_sdk::contractimport!` — so a registered contract whose wasm was never published still works. - Fail compilation if the contract is flagged as compromised (#38, #52). No on-chain getter exists, so read the registry's ContractEntry persistent ledger entry directly via RPC (key (Symbol("CR"), <canonical name>); a 3-element vec == flagged), behind a new `fetch-contract-id --reject-flagged`. - Online builds no longer trust the cached .id, so a contract flagged after the first build can't slip through; the .id/.wasm caches are the offline (STELLAR_NO_REGISTRY=1) fallback, and env override / offline are the only explicit opt-outs of the flag check. - Self-contained rustdoc (no import_contract_client! reference). New: stellar_registry_build::Registry::is_contract_flagged (raw ledger read), fetch_contract_id --reject-flagged. Design/plan docs get a post-review revision note. Verified end to end on testnet: fetch-contract-id --reject-flagged returns the address when unflagged, errors "contract `oz` is flagged as compromised" once flag_contract sets it, and the plain lookup is unchanged. Unit tests + pedantic clippy pass. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Q5b9Nj9oNVLjeRetxCeyxP
chadoh
left a comment
There was a problem hiding this comment.
Better but still a lot to improve here
| @@ -0,0 +1,213 @@ | |||
| # `import_contract!` macro — Design | |||
There was a problem hiding this comment.
I... kind of hate this docs/superpowers pattern. It seems definitionally like a bunch of bloat for the sake of AI. Convince me that it's not. Convince me that this will ever be useful to a human, and should be checked into source control.
My thoughts on why we're better off without it:
- It's a LOT to read through (no human's going to do that, c'mon)
- It's going to drift from the implementation VERY quickly
- The decisions about why we built things the way we did should already in the git log, and at least in GitHub issues/PRs
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Q5b9Nj9oNVLjeRetxCeyxP
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Q5b9Nj9oNVLjeRetxCeyxP
Implement the import_contract! macro helper functions in TDD style: - Create crate skeleton with proc-macro lib type - Wire workspace dependencies (proc-macro2, quote, syn) - Add 6 pure helper functions: mod_name_from, split_version, env_var_name, validate_contract_id, cache_id_path, manifest - Add comprehensive unit tests for all helpers (5 tests, all passing) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Q5b9Nj9oNVLjeRetxCeyxP
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Q5b9Nj9oNVLjeRetxCeyxP
Add resolve_address (fully injectable for testability) and fetch_contract_id (shell-out to stellar CLI) to implement address resolution precedence: env var > cache > registry lookup. Includes 4 unit tests verifying the resolution order (env override, cache fallback, no-registry error, fetch). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Q5b9Nj9oNVLjeRetxCeyxP
Implement struct Input with syn::parse::Parse trait to parse the macro's input (env expression and contract name as bare ident or string literal). Implement fn expand() to emit a block expression that delegates wasm import to import_contract_client! macro and constructs the client bound to the resolved contract address. Add codegen test module with 3 tests covering parsing and code generation. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Q5b9Nj9oNVLjeRetxCeyxP
…istry Adds the #[proc_macro] entry point that ties together the pure helpers, build-time address resolution, and codegen from the prior tasks, and re-exports it as stellar_registry::import_contract for consumers. This wires every previously-dead helper into the entry point, so no dead_code warnings remain and no #[allow(dead_code)] was needed. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Q5b9Nj9oNVLjeRetxCeyxP
Prevents a compiler panic on names like "foo/" or "@v1.0.0" (empty module identifier) and stops a malformed fetch-contract-id response from poisoning the .id cache. Findings from the final whole-branch review. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Q5b9Nj9oNVLjeRetxCeyxP
Addresses review on #17 ("stop slopping me bro"): the macro was built by analogy to import_contract_client! and conflated a contract with its wasm. - Drop @Version. A deployed contract has no version (only a wasm does); reject `@` with a clear compile_error!. - Stop delegating codegen to import_contract_client!(name), which resolves a wasm by name and wrongly assumes contract-name == wasm-name. Instead fetch the deployed contract's own on-chain wasm by address (`stellar contract fetch --id`) and inline `soroban_sdk::contractimport!` — so a registered contract whose wasm was never published still works. - Fail compilation if the contract is flagged as compromised (#38, #52). No on-chain getter exists, so read the registry's ContractEntry persistent ledger entry directly via RPC (key (Symbol("CR"), <canonical name>); a 3-element vec == flagged), behind a new `fetch-contract-id --reject-flagged`. - Online builds no longer trust the cached .id, so a contract flagged after the first build can't slip through; the .id/.wasm caches are the offline (STELLAR_NO_REGISTRY=1) fallback, and env override / offline are the only explicit opt-outs of the flag check. - Self-contained rustdoc (no import_contract_client! reference). New: stellar_registry_build::Registry::is_contract_flagged (raw ledger read), fetch_contract_id --reject-flagged. Design/plan docs get a post-review revision note. Verified end to end on testnet: fetch-contract-id --reject-flagged returns the address when unflagged, errors "contract `oz` is flagged as compromised" once flag_contract sets it, and the plain lookup is unchanged. Unit tests + pedantic clippy pass. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Q5b9Nj9oNVLjeRetxCeyxP
This builds on the initial PrefixedName type but adds a Versioned type which makes checking for versions on wasm parsable. Also addressed other issues in PR review.
29d0cf1 to
0acb8ae
Compare
import_contract! macro (stellar-scaffold/cli#419)import_contract! macro + consolidate registry macros (stellar-scaffold/cli#419)
chadoh
left a comment
There was a problem hiding this comment.
Didn't have time to give this a full deep-dive today, but from what I saw it's looking great! One comment so far. Happy to have you push back on this idea, but I think it could be nice. See below.
| //! The `name` module (typed registry names) is always available and | ||
| //! dependency-light so proc-macro crates can use it. Everything that talks to | ||
| //! the network — `contract`, `registry`, `error` — sits behind the default | ||
| //! `cli` feature, which pulls in the full stellar-cli stack. |
There was a problem hiding this comment.
This is cool and clever but I do wonder if creating a separate crates/stellar-registry-name would be a more straightforward way to handle it
There was a problem hiding this comment.
I'm confused; this section is about how the modules in this crate are exposed. I do like that idea though since it removes the download at build time part but wouldn't allow for stopping flagged contracts.
There was a problem hiding this comment.
Yeah, I'm saying the clever "cli features" stuff, and corresponding "skip default features" thing done by the proc-macro, wouldn't be necessary if we had a stripped-down stellar-registry-name crate that only did the proc-macro-compatible stuff. Then that crate can be used in both places, and would be easier for external parties to consume and use too, if they care to, without the "embeds stellar cli by default" footgun.
Implements
import_contract!(stellar-scaffold/cli#419) and consolidates all registry macros into this repo:import_contract!,import_contract_client!, andimport_asset!now live incrates/stellar-registry-macroand are re-exported fromstellar-registry. Thestellar-scaffold-macrodependency is gone.What
stellar_registry::import_contract!(env, name)returns a type-safe sorobanClientalready bound to the named contract's deployed on-chain address, resolved at build time:Unlike
import_contract_client!(which imports a published wasm, optionally@version),import_contract!imports a deployed contract: it has no version, and its client types are generated from the deployed instance's own wasm (stellar contract fetch --id …), so contracts whose wasm was never published to the registry still work.Parse, don't validate
Names are now typed in
stellar-registry-build::nameand parsing is the only way to construct them:Prefixed—nameorchannel/name. Rejects empty names,@(deployed contracts have no version), multiple slashes, and invalid characters. Private fields;name()/channel()/mod_name()/canonical_name()accessors.Versioned—Prefixed+ optional@version(leadingvtolerated). A malformed version is an error, never silently "latest" (the old code dropped@v1.0.0on the floor).import_contract!parsesPrefixed,import_contract_client!parsesVersioned— so "contracts have no version" is enforced by the type, not a string check. The same types back the CLI's clap args (14 commands), so bad names fail at arg parsing with real messages.Address resolution & caching
stellar registry fetch-contract-idevery build — a cached id is deliberately ignored so a contract flagged after the first build can't slip through. Result cached attarget/stellar/<network>/deployed/[<channel>__]<mod_name>.id, wasm beside it. The wasm is refetched when the resolved address changes (redeploy under the same name).deployed/namespace + channel-qualified stems mean the cache can never collide with registry-downloaded wasms, workspace-built wasms, or the same name on a different channel.STELLAR_NO_REGISTRY=1forbids network calls and requires the cached id + wasm.STELLAR_CONTRACT_ID_<NAME>override is removed (per review).Flagged contracts (breaking)
Registry::fetch_contract_id(build crate) now rejects flagged contracts by default;fetch_contract_id_uncheckedis the explicit opt-out.stellar registry fetch-contract-iderrors on flagged contracts unless--force;create-aliashonors its existing--forcethe same way.import_contract!fails compilation on a flagged contract (with an up-to-date plugin).Errors now say which failure occurred (CLI missing vs plugin missing vs plugin too old vs flagged vs not found, with the exact stderr) and link the registry explorer on testnet/mainnet.
Dependency footprint
stellar-registry-macrois re-exported fromstellar-registry, which user contracts depend on — so it must stay light.stellar-registry-buildnow gates everything network-facing behind the defaultclifeature; withdefault-features = false(what the macro uses) its tree is semver + thiserror, not the stellar-cli stack.Removed
docs/superpowers/(per review)named_registry.rs(superseded byname::Prefixed)stellar-scaffold-macrodependency🤖 Generated with Claude Code