Skip to content

feat: import_contract! macro + consolidate registry macros (stellar-scaffold/cli#419) - #17

Merged
chadoh merged 31 commits into
mainfrom
feat/import-contract-macro
Jul 27, 2026
Merged

feat: import_contract! macro + consolidate registry macros (stellar-scaffold/cli#419)#17
chadoh merged 31 commits into
mainfrom
feat/import-contract-macro

Conversation

@willemneal

@willemneal willemneal commented Jul 3, 2026

Copy link
Copy Markdown
Collaborator

Implements import_contract! (stellar-scaffold/cli#419) and consolidates all registry macros into this repo: import_contract!, import_contract_client!, and import_asset! now live in crates/stellar-registry-macro and are re-exported from stellar-registry. The stellar-scaffold-macro dependency is gone.

What

stellar_registry::import_contract!(env, name) returns a type-safe soroban Client already bound to the named contract's deployed on-chain address, resolved at build time:

let dao = stellar_registry::import_contract!(env, our_dao);
dao.create_proposal(/* ... */);

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::name and parsing is the only way to construct them:

  • Prefixedname or channel/name. Rejects empty names, @ (deployed contracts have no version), multiple slashes, and invalid characters. Private fields; name() / channel() / mod_name() / canonical_name() accessors.
  • VersionedPrefixed + optional @version (leading v tolerated). A malformed version is an error, never silently "latest" (the old code dropped @v1.0.0 on the floor).

import_contract! parses Prefixed, import_contract_client! parses Versioned — 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

  • Online: stellar registry fetch-contract-id every build — a cached id is deliberately ignored so a contract flagged after the first build can't slip through. Result cached at target/stellar/<network>/deployed/[<channel>__]<mod_name>.id, wasm beside it. The wasm is refetched when the resolved address changes (redeploy under the same name).
  • The 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=1 forbids network calls and requires the cached id + wasm.
  • The 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_unchecked is the explicit opt-out.
  • stellar registry fetch-contract-id errors on flagged contracts unless --force; create-alias honors its existing --force the 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-macro is re-exported from stellar-registry, which user contracts depend on — so it must stay light. stellar-registry-build now gates everything network-facing behind the default cli feature; with default-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 by name::Prefixed)
  • stellar-scaffold-macro dependency

🤖 Generated with Claude Code

@chadoh chadoh left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

stop slopping me bro

Comment thread crates/stellar-registry-macro/src/lib.rs Outdated
Comment thread crates/stellar-registry-macro/src/lib.rs Outdated

@chadoh chadoh left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This also needs to fail compilation if the requested contract is flagged

willemneal added a commit that referenced this pull request Jul 14, 2026
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 chadoh left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Better but still a lot to improve here

Comment thread crates/stellar-registry-cli/src/commands/fetch_contract_id.rs Outdated
Comment thread crates/stellar-registry-macro/src/lib.rs Outdated
Comment thread crates/stellar-registry-macro/src/lib.rs Outdated
Comment thread crates/stellar-registry/src/lib.rs Outdated
Comment thread docs/superpowers/specs/2026-07-02-import-contract-macro-design.md Outdated
willemneal and others added 12 commits July 15, 2026 12:48
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.
@willemneal
willemneal force-pushed the feat/import-contract-macro branch from 29d0cf1 to 0acb8ae Compare July 16, 2026 13:01
@willemneal
willemneal requested a review from chadoh July 16, 2026 13:54
@willemneal willemneal changed the title feat: import_contract! macro (stellar-scaffold/cli#419) feat: import_contract! macro + consolidate registry macros (stellar-scaffold/cli#419) Jul 16, 2026

@chadoh chadoh left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread crates/stellar-registry-build/src/lib.rs Outdated
Comment thread crates/stellar-registry-cli/src/commands/create_alias.rs
- Remove stale-by-the-time-it-lands parenthetical about needing to be on
  the latest Registry CLI
- Correct occurrences of "contract" when we meant "wasm"
Comment thread crates/stellar-registry-macro/src/contract_client.rs Outdated
Comment thread crates/stellar-registry-macro/src/contract.rs
chadoh added 2 commits July 24, 2026 14:42
Mirror the `as_deref() == Ok("1")` syntax used by sibling `contract.rs`
Comment thread crates/stellar-registry-macro/src/contract.rs Outdated
willemneal and others added 2 commits July 24, 2026 15:08
Co-authored-by: Chad Ostrowski <221614+chadoh@users.noreply.github.com>
Co-authored-by: Chad Ostrowski <221614+chadoh@users.noreply.github.com>
Comment thread crates/stellar-registry-macro/src/contract.rs
Comment thread crates/stellar-registry-macro/src/contract.rs
Comment thread crates/stellar-registry-macro/src/asset.rs
chadoh
chadoh previously approved these changes Jul 24, 2026

@chadoh chadoh left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Dude this looks awesome now! I love how much stellar-registry-name cleaned everything up! Thanks for sticking it out with me and adjusting to all my giant architectural change requests.

I have a few small nits & cleanup notes above. After you address those, this looks ready to go!

🎉 🎉 🎊 👏🏼 👏🏼

@chadoh chadoh left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wait actually the docs for stellar_registry crate are stale; let me fix them real quick

chadoh
chadoh previously approved these changes Jul 24, 2026

@chadoh chadoh left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok all good let's gooooo (but do address my nits & suggestions above first)

@chadoh
chadoh force-pushed the feat/import-contract-macro branch from b9eb9c7 to 9896b56 Compare July 27, 2026 16:38
chadoh added 2 commits July 27, 2026 12:55
Can't use the variables directly in the `format!`, need to call
`.display()` explicitly.

```
error[E0277]: `std::path::Path` doesn't implement `std::fmt::Display`
  --> crates/stellar-registry-macro/src/contract.rs:90:88
```

But in `contract_client.rs` we want to show the full path, not just the
`wasm.name()`. Since `Versioned` implements `Display`, we can shorten
this to put `wasm` in the `format!`.
`canonical_name` returns the name as it should be formatted _on chain_,
which means _no underscores_. It replaces underscores with hyphens.

`mod_name` returns a name appropriate for use as a Rust module, which
means _no hyphens_. It replaces hyphens with underscores.

This also adds a test to ensure that `mod_name` lowercases the input.
This shouldn't happen because the canonical on-chain form of the name is
not allowed to have capital letters.
@chadoh
chadoh force-pushed the feat/import-contract-macro branch from 9896b56 to e484fad Compare July 27, 2026 16:59

@chadoh chadoh left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅

@chadoh
chadoh merged commit f87668a into main Jul 27, 2026
5 checks passed
@chadoh
chadoh deleted the feat/import-contract-macro branch July 27, 2026 17:15
This was referenced Jul 27, 2026
chadoh added a commit that referenced this pull request Jul 27, 2026
## 🤖 New release

* `stellar-registry-name`: 0.1.0
* `stellar-registry-macro`: 0.1.0
* `stellar-registry`: 0.0.11 -> 0.1.0
* `stellar-registry-build`: 0.0.9 -> 0.1.0
* `stellar-registry-cli`: 0.0.22 -> 0.1.0 (✓ API compatible changes)

<details><summary><i><b>Changelog</b></i></summary><p>

## `stellar-registry-name`

<blockquote>

##
[0.0.1](https://github.com/stellar-registry/cli/releases/tag/stellar-registry-name-v0.1.0)
- 2026-07-27

### Added

- [**breaking**] `import_contract!` macro + consolidate registry macros
(stellar-scaffold/cli#419)
([#17](#17))
</blockquote>

## `stellar-registry-macro`

<blockquote>

##
[0.0.1](https://github.com/stellar-registry/cli/releases/tag/stellar-registry-macro-v0.1.0)
- 2026-07-27

### Added

- [**breaking**] `import_contract!` macro + consolidate registry macros
(stellar-scaffold/cli#419)
([#17](#17))
</blockquote>

## `stellar-registry`

<blockquote>

##
[0.0.12](stellar-registry-v0.0.11...stellar-registry-v0.1.0)
- 2026-07-27

### Added

- [**breaking**] `import_contract!` macro + consolidate registry macros
(stellar-scaffold/cli#419)
([#17](#17))
</blockquote>

## `stellar-registry-build`

<blockquote>

##
[0.0.10](stellar-registry-build-v0.0.9...stellar-registry-build-v0.1.0)
- 2026-07-27

### Added

- [**breaking**] `import_contract!` macro + consolidate registry macros
(stellar-scaffold/cli#419)
([#17](#17))
</blockquote>

## `stellar-registry-cli`

<blockquote>

##
[0.0.23](stellar-registry-cli-v0.0.22...stellar-registry-cli-v0.1.0)
- 2026-07-27

### Added

- [**breaking**] `import_contract!` macro + consolidate registry macros
(stellar-scaffold/cli#419)
([#17](#17))
</blockquote>


</p></details>

---
This PR was generated with
[release-plz](https://github.com/release-plz/release-plz/).

---------

Co-authored-by: aha-release-plz[bot] <211513279+aha-release-plz[bot]@users.noreply.github.com>
Co-authored-by: Chad Ostrowski <221614+chadoh@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants