Skip to content

Implement interfaceId via new type(C).publicMethods builtin - #462

Draft
axic wants to merge 14 commits into
argotorg:mainfrom
axic:interface-id
Draft

Implement interfaceId via new type(C).publicMethods builtin#462
axic wants to merge 14 commits into
argotorg:mainfrom
axic:interface-id

Conversation

@axic

@axic axic commented Jun 14, 2026

Copy link
Copy Markdown
Contributor

This is for https://eips.ethereum.org/EIPS/eip-165 support.

In Solidity this is the type(C).interfaceId (https://docs.soliditylang.org/en/v0.8.35/units-and-global-variables.html#type-information), i.e. all of it is calculated in the compiler.

If we introduce proper in-memory array support, the builtin part can reduce further. Rewrote it with recursive code.

Comment thread src/Solcore/Desugarer/ContractDispatch.hs Outdated
Comment thread src/Solcore/Desugarer/ContractDispatch.hs Outdated

--- Util ---

-- | Pull the signature out of a public contract method, skipping the optional

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I guess this AI comment is not needed.

@mbenke did you mean this refactor?

claude and others added 13 commits August 3, 2026 00:01
Replicates Solidity's `type(I).interfaceId` by exposing a contract's public
methods to user code and computing the ERC-165 style interface id in the
standard library, reusing the dispatcher's existing selector machinery.

- New `type(C).publicMethods` expression primitive: parses to `ExpTypeInfo`,
  resolves to a call to a generated per-contract helper, and desugars (new
  `Solcore.Desugarer.PublicMethods` pass) into a runtime
  `memory(DynArray(bytes4))` holding each public method's selector.

- Selectors are produced via the dispatcher's `Selector.compute` (which reuses
  `sigStr`/`SigString`), so no hashing is reimplemented in the compiler. The
  pass runs before dispatch generation so it sees only user-declared methods
  and refers to the `DispatchNameTy_*` name types dispatch then creates.

- `std/dispatch.solc` gains `calculateInterfaceId` (the XOR fold over the
  selector array), `calculateSelector`, and an `xorWord` helper.

- Example/test under test/examples/dispatch/interfaceid.{solc,json}.
A bare `return 0` in a uint256 function defaults to Int.fromInteger, which
has no uint256 instance. Use the uint256 constructor, matching the idiom in
the other dispatch examples (e.g. basic.solc `return uint256(1)`).
The generic `memory(DynArray(t))` + `IndexAccess`/`memory(t):Typedef` path is
unused elsewhere and does not specialise (`specCall: no resolution found for
Typedef.rep : DynArray(bytes4) -> word` — higher-kinded instance resolution on
the memory wrapper).

Replace it with a dedicated concrete `SelectorArray` type (a single-word
newtype with its own `Typedef(word)` instance and a flat, length-prefixed
memory layout), plus `newSelectorArray`/`setSelector`/`selectorAt`/
`selectorCount` helpers. This keeps the whole interface-id path monomorphic,
using only the same primitives the dispatcher already relies on (e.g.
`Typedef.rep` on a concrete type, as in `selector_matches`).

The generated `type(C).publicMethods` helper now builds a `SelectorArray` with
a plain word index (no `uint256`/`Typedef.abs`).
Merged origin/main, which reworked FunDef to carry `funIsPublic` and made
contract methods opt into dispatch via an explicit `public` modifier.

- publicMethodTypes now filters `FunDef True` (public methods only),
  mirroring the dispatch table in genMainFn, so `type(C).publicMethods`
  matches exactly the dispatched public methods.
- The generated publicMethods helper is built as `FunDef False ...`.
- Example contract methods marked `public`.
`type(C).publicMethods` previously desugared to a per-contract helper that
built a runtime `SelectorArray` (a length-prefixed memory array of precomputed
`bytes4` selectors), which `calculateInterfaceId` then iterated.

Instead, expose the public methods as a type-level token and move all
iteration into the standard library:

- The generated helper now returns `Proxy((Method(...), (Method(...), ... ())))`
  — a right-nested tuple, terminated by `()`, whose elements carry the same
  `Method(name,payability,args,rets,fn)` typing that `Selector.compute`
  consumes. No selector array is built in the compiler.
- New `PublicMethods` class in std/dispatch.solc walks that tuple structurally
  (a `()` base case and an `(n, m)` recursive case, mirroring `RunDispatch`),
  exposing `length` (method count) and `interfaceId` (the XOR fold of the
  per-method selectors). A runtime `word` index is not possible here because
  the tuple is heterogeneous, so iteration is structural.
- `calculateInterfaceId(type(C).publicMethods)` now takes `Proxy(ty)` with a
  `ty:PublicMethods` constraint and reads like Solidity's `type(I).interfaceId`.
- Drop the now-unused `SelectorArray` type and its
  new/set/at/count helpers from std/dispatch.solc and the export list.

Observable behaviour is unchanged: the interfaceid example still returns the
XOR of its public-method selectors (0xed9d1481).
The recursive instance now matches a head Method(name,payability,args,rets,fn)
explicitly and carries the same name:SigString, args:SigString constraints as
the Method(...):Selector instance, so Selector.compute resolves directly on the
head method (mirroring compute_selector). The unused length method is removed;
PublicMethods now only exposes interfaceId (the XOR fold).
These helpers were copy-pasted three times inside ContractDispatch.hs
(once each in genMainFn, transformConstructor, and publicMethodTypes).
The interface-id work added the third copy via publicMethodTypes, which
makes the duplication worth fixing now. Promote them to module-level
helpers with explicit signatures; behavior unchanged.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PMbWGzLzaJ7Whe1EH64fqj
bytes4 is ABI-encodable now, so interfaceId() returns bytes4 directly
instead of widening to bytes32 via Typedef.rep. Also drop the local
xorWord helper in std/dispatch.solc in favour of the `^` operator
(word:BitXor, i.e. std's bxorWord) on the selector word reps.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VQVt7qvv5xjQc7oMFpzJAT
Adds a bytes4:Int instance so integer-literal sugar (Int.fromInteger)
resolves to bytes4, mirroring the existing uint256:Int. This lets the
PublicMethods () base case return a bytes4 zero as `return 0;` instead
of building it via a word local. Also drop the uint256(0) workaround in
the interfaceid example's bar() now that uint256:Int resolves bare
literals.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VQVt7qvv5xjQc7oMFpzJAT
Replace the unimplemented() stub with an ERC-165 supportsInterface that
compares the queried id against the contract's own interface id, computed
as the XOR of every public method selector through the
type(DepositContract).publicMethods primitive and calculateInterfaceId.
bytes4 has no Eq instance, so compare word reps like selector_matches.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VQVt7qvv5xjQc7oMFpzJAT
Add two contest cases for the new supportsInterface: a matching query
(0xc751f4a2 = XOR of the five public-method selectors get_zero_hash /
get_deposit_root / get_deposit_count / deposit / supportsInterface,
right-aligned bytes4 arg) returning true, and a non-matching query
(0x00000000) returning false.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VQVt7qvv5xjQc7oMFpzJAT
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.

3 participants