Skip to content

fix(erc20): gate token metadata calls on bytecode selectors#252

Merged
spalen0 merged 1 commit into
mainfrom
fix/erc20-metadata-bytecode-gate
May 29, 2026
Merged

fix(erc20): gate token metadata calls on bytecode selectors#252
spalen0 merged 1 commit into
mainfrom
fix/erc20-metadata-bytecode-gate

Conversation

@spalen0
Copy link
Copy Markdown
Collaborator

@spalen0 spalen0 commented May 29, 2026

Problem

The AI explainer enriches every address in a transaction (each call target + every address-typed calldata arg) by calling symbol()/decimals() and catching the failure. Most addresses aren't tokens, so we fire eth_calls we expect to fail. On a MAPLE safe tx this surfaced as repeated:

WARNING utils.web3: Could not decode contract function call to symbol() with return data: b''
WARNING utils.web3: Switching to provider: ...

— a non-ERC20 address returning empty data, retried across every RPC.

Fix

Gate the metadata call on positive evidence: only call symbol()/decimals() when the contract bytecode actually dispatches both selectors (95d89b41 / 313ce567).

  • EOAs (no code) → skipped, no call.
  • Non-token contracts (selectors absent) → skipped, no call.
  • Proxy tokens (USDC etc.) → a proxy stub carries none of the impl's selectors, so we resolve the implementation via utils.proxy.get_current_implementation and scan its bytecode too. Without this, proxy tokens would be false-negatives.
  • The existing try/except stays as a backstop for exotic dispatch (metamorphic, unusual proxies).

get_code results feed the same per-address cache, so a non-token address costs at most one eth_getCode (+ a proxy-slot read) once, then is cached as a miss.

Why not a different check?

There's no reliable static "is ERC20" check — ERC20 has no ERC165 supportsInterface, and many tokens are proxies. Bytecode-selector inspection (with proxy resolution) is the cheapest scheme that gives a positive signal before calling, while staying proxy-safe.

Testing

  • tests/test_erc20_metadata.py rewritten to mock the bytecode gate: token, EOA-skip, non-token-skip, proxy-resolution, revert-backstop, and cache-miss-no-refetch.
  • Full suite: 410 passed, 4 skipped. ruff clean.

Note

This is independent of the related one-line web3 change on PR #251 (treating decode failures as non-retryable). This PR removes the call entirely in the common case; that one quiets the retry storm if a probe still slips through.

🤖 Generated with Claude Code

Before calling symbol()/decimals(), confirm the contract bytecode actually
dispatches both selectors (95d89b41 / 313ce567). EOAs and non-token
contracts are skipped without a blind eth_call, so the AI explainer no
longer probes every address in a tx and fails.

Proxies delegate via fallback and carry none of the implementation's
selectors, so a bare scan would false-negative proxy tokens (USDC etc.).
We resolve the implementation via utils.proxy.get_current_implementation
and scan its bytecode too. The existing try/except stays as a backstop for
exotic dispatch.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@spalen0 spalen0 marked this pull request as ready for review May 29, 2026 10:47
@spalen0 spalen0 merged commit ab27a0a into main May 29, 2026
2 checks passed
@spalen0 spalen0 deleted the fix/erc20-metadata-bytecode-gate branch May 29, 2026 10:48
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.

1 participant