Skip to content

feat: Extend #[export] with per-function transport/encoding opt-in (scale and ethabi)#1303

Merged
vobradovich merged 17 commits intomasterfrom
vo/export-scale-ethabi
Apr 17, 2026
Merged

feat: Extend #[export] with per-function transport/encoding opt-in (scale and ethabi)#1303
vobradovich merged 17 commits intomasterfrom
vo/export-scale-ethabi

Conversation

@vobradovich
Copy link
Copy Markdown
Member

Resolves #1263 .

Summary

Extends #[export] on service methods with per-transport opt-in flags so a method can be exposed only through the Gear/SCALE dispatch path, only through the Solidity/ethexe ABI path, or both:

#[export(scale)] // SCALE dispatch only
#[export(ethabi)] // Solidity ABI dispatch only
#[export(scale, ethabi)] // both (same as bare #[export])
#[export(ethabi, payable)] // ethabi + payable

Plain #[export] remains backward-compatible and implies both transports.

Why

Before this change, every #[export] method was generated into both the SCALE and Solidity ABI dispatch paths unconditionally. That made #[export] too coarse:

  • A method meant only for Gear callers was still forced through abi_decode_params / abi_encode_sequence, requiring its types to satisfy SolValue.
  • A method meant only for Solidity callers was still forced through SCALE Decode/Encode, requiring its types to satisfy parity_scale_codec bounds — blocking ABI-native primitives like alloy_primitives::Address / B256.

Transport selection decouples contract membership from dispatch visibility: #[export] still controls whether a method is part of the service's contract (IDL, INTERFACE_ID, method metadata), while the new flags control which runtime dispatch paths actually route to it.

Core Invariant

#[export] controls contract membership; transport flags control runtime dispatch only.

  • IDL metadata, CommandsMeta / QueriesMeta, MethodMetadata, method hashes, and INTERFACE_ID are computed from the full exported method surface regardless of transport visibility.
  • Single-transport methods are annotated in the IDL with #[annotate(scale)] or #[annotate(ethabi)] so downstream consumers can see the restriction without losing the method itself.
  • Only runtime dispatch generation (try_handle for SCALE, try_handle_solidity + ServiceSignature::METHODS for ABI) is filtered by transport.

Key Changes

  • rs/macros/core/src/export/args.rs — parse scale / ethabi flags; reject duplicate flags; reject payable without ethabi (with spans anchored on the offending token).
  • rs/macros/core/src/shared.rs — propagate transport flags through InvocationExport and FnBuilder; add has_scale_transport() / has_ethabi_transport() helpers.
  • rs/src/gstd/mod.rs — refactor InvocationIo into a marker trait (type Params; with no Decode bound); extract SCALE encode/decode helpers to free functions (decode_invocation_params, encode_invocation_payload, encode_invocation_payload_with_id) so ethabi-only params structs don't have to satisfy SCALE codec bounds.
  • rs/src/gstd/macros.rs — extend invocation_io! with a decode = false variant that omits #[derive(Decode)] and #[codec(...)] for ethabi-only params.
  • rs/macros/core/src/service/exposure.rs — filter generated SCALE dispatch arms by has_scale_transport(); migrate generated encode calls to the new encode_invocation_payload free function.
  • rs/macros/core/src/service/ethexe.rs — filter generated Solidity dispatch arms and ServiceSignature::METHODS by has_ethabi_transport().
  • rs/macros/core/src/service/meta.rs — emit #[annotate(scale)] / #[annotate(ethabi)] for single-transport methods; metadata and interface hashing remain unfiltered.
  • Alloy primitive support (rs/reflect-hash, rs/type-registry) — TypeInfo + ReflectHash for alloy_primitives::Address / B256, mapped to the same logical IDL shapes as H160 / H256, so ethabi-only methods can use ABI-native types in IDL/hash generation.

Test Plan

  • Macro snapshot tests (insta) cover scale-only, ethabi-only, dual, mixed, and backward-compatible bare #[export].
  • Snapshot asserts CommandsMeta / QueriesMeta / METHODS / INTERFACE_ID include every exported method regardless of transport.
  • Snapshot asserts single-transport methods carry #[annotate(scale)] / #[annotate(ethabi)].
  • Snapshot verifies ethabi-only params structs are generated without #[derive(Decode)].
  • Trybuild UI tests reject duplicate ethabi, duplicate scale, and payable without ethabi.
  • Trybuild pass fixture compiles an ethabi-only method using alloy_primitives::Address / B256.
  • Runtime wrong-transport dispatch (try_handle on ethabi-only, try_handle_solidity on scale-only) returns None.

Out of Scope

  • Program constructors (#[ctor] / program_ctor!) remain dual-transport. Constructor transport selection is a separate design problem.
  • New IDL primitive names for Address / B256 are deferred; they reuse H160 / H256 shapes for now.
  • Cross-transport runtime diagnostics stay at the existing "Unknown request" fall-through.

@vobradovich vobradovich self-assigned this Apr 15, 2026
Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request introduces explicit transport selection for service exports, allowing methods to be specifically targeted for scale, ethabi, or both. The macro system and InvocationIo trait were refactored to support this, including the ability to define ABI-only metadata without SCALE decoding requirements. Additionally, support for alloy-primitives was added to the reflection and type registry systems. Feedback was provided regarding the use of let_chains in the macro argument parsing logic, suggesting a nested if statement to maintain compatibility with older Rust versions.

Comment thread rs/macros/core/src/export/args.rs
@vobradovich vobradovich changed the title Vo/export scale ethabi feat: Extend #[export] with per-function transport/encoding opt-in (scale and ethabi) Apr 15, 2026
@vobradovich vobradovich marked this pull request as ready for review April 15, 2026 21:12
@vobradovich vobradovich requested a review from m62624 April 15, 2026 21:12
vobradovich and others added 3 commits April 16, 2026 00:36
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@vobradovich vobradovich merged commit e9cf166 into master Apr 17, 2026
4 checks passed
@vobradovich vobradovich deleted the vo/export-scale-ethabi branch April 17, 2026 10:04
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.

Extend #[export] with per-function transport/encoding opt-in (scale and eth)

2 participants