Skip to content

Address-taking opcode scans reject codeless accounts - #141

Merged
thedavidmeister merged 2 commits into
mainfrom
2026-08-19-issue-83-address-opcode-scans
Aug 19, 2026
Merged

thedavidmeister merged 2 commits into
mainfrom
2026-08-19-issue-83-address-opcode-scans

Conversation

@thedavidmeister

Copy link
Copy Markdown
Contributor

Closes #83

Implements the ruling on #83 (2026-08-19, #83 (comment)) exactly: the remaining opcode-scan half takes the same boundary rule as the metamorphic pair from PR #136. Address-taking scanEVMOpcodesPresentInBytecode(address) and scanEVMOpcodesReachableInBytecode(address) entry points revert with CodelessAccount(address) (the E+D class from #55) on a codeless account, before delegating to the existing bytes functions. Library-only: IExtrospectV1 stays untouched, same as PR #136 — the interface surface for a new deploy is ruled separately (#139).

What changed

  • LibExtrospectBytecode gains the address-taking entry points scanEVMOpcodesPresentInBytecode(address) and scanEVMOpcodesReachableInBytecode(address): both read the account's code, revert CodelessAccount(account) when there is none, and delegate to the bytes functions otherwise. An account with no code can gain any code later — an unoccupied CREATE2 target, a self-destructed account between incarnations, or an EOA that can gain code by EIP-7702 delegation — so a zero bitmap for it would vouch for nothing, and only the address boundary has the information to refuse. Types-only returns with explicit return, per org convention.
  • Bytes-taking scans stay total over bytes. scanEVMOpcodesPresentInBytecode(bytes) / scanEVMOpcodesReachableInBytecode(bytes) keep their behaviour, including the zero bitmap over empty bytes pinned since PR Reject codeless accounts in address-taking absence checks #136; their NatSpec now cross-references the address entry points as the way to bind a scan to an account.
  • README Opcode scanning section documents the address entry points, paralleling the metamorphic section.
  • IExtrospectV1 untouched — no function or error added to or removed from the interface; git diff main touches no file under src/interface/.

Out of scope, untouched: #139 (interface-level surface for a new deploy), #54/#68/#82 (proxy and delegation resolution — the address entry points bind a verdict to the account's current code, nothing more).

QA

  • Discriminating tests:

    • testScanEVMOpcodesPresentAddressRevertsOnCodelessAccount, testScanEVMOpcodesReachableAddressRevertsOnCodelessAccount — codeless account reverts CodelessAccount(0xC2) from both address entry points, expected revert data built with abi.encodeWithSelector(CodelessAccount.selector, codeless) so the error AND its address payload are pinned; the bytes entry points still scan the same account's empty code to zero (testScanEVMOpcodesPresentCodelessAccount, testScanEVMOpcodesReachableCodelessAccount, unchanged from PR Reject codeless accounts in address-taking absence checks #136). Each fails on base: committed first (52852b1) against a guard-less scaffold and observed failing behaviourally ("next call did not revert as expected"; 396 passed, 5 failed = these 2 + the 3 environmental fork tests), then the guard commit (4d8b502) turned them green.
    • Address entry point delegation: test*AddressSelfdestruct (SELFDESTRUCT bit set and exact agreement with the bytes scan over a deployed contract's code), test*AddressRevertsOnEOF (EOFBytecodeNotSupported through the delegate on etched EOF code), test*AddressEquivalenceFuzz (address vs bytes agreement over nonempty non-EOF etched code, via LibExtrospectTestEtch.assumeEtch). These pass against the scaffold by construction; the mutation table below is what discriminates them.
  • Mutations applied (each applied to the working tree, nix develop -c forge test --no-match-path "*fork*" run in full, then reverted; baseline for that command is 398 passed, 0 failed):

    # Mutant (applied to src, then reverted) Outcome
    M1 scanEVMOpcodesPresentInBytecode(address): codeless guard deleted KILLED: testScanEVMOpcodesPresentAddressRevertsOnCodelessAccount ("next call did not revert as expected"); 397 passed, 1 failed
    M2 scanEVMOpcodesReachableInBytecode(address): codeless guard deleted KILLED: testScanEVMOpcodesReachableAddressRevertsOnCodelessAccount (same shape); 397 passed, 1 failed
    M3 Present guard reverts CodelessAccount(address(0)) instead of the account KILLED: revert-data mismatch CodelessAccount(0x0000...0000) != CodelessAccount(0x0...00c2) — the error's address payload is pinned; 397 passed, 1 failed
    M4 Reachable guard reverts CodelessAccount(address(0)) instead of the account KILLED: same shape on the Reachable test; 397 passed, 1 failed
    M5 Present address entry point delegates to the Reachable bytes scan KILLED: testScanEVMOpcodesPresentAddressEquivalenceFuzz (counterexample where reachable != present); 397 passed, 1 failed
    M6 Reachable address entry point delegates to the Present bytes scan KILLED: testScanEVMOpcodesReachableAddressEquivalenceFuzz (counterexample args=[0x...079f]); 397 passed, 1 failed
    M7 Present guard boundary bytecode.length == 0 -> == 1 KILLED: testScanEVMOpcodesPresentAddressRevertsOnCodelessAccount (length 0 no longer reverts) and testScanEVMOpcodesPresentAddressEquivalenceFuzz with a 1-byte counterexample (args=[0x32] reverting CodelessAccount(0xbEEF)); 396 passed, 2 failed
    M8 Reachable guard boundary bytecode.length == 0 -> == 1 KILLED: same pair on the Reachable side (args=[0x49]); 396 passed, 2 failed

    All 8 mutants killed, 0 survivors.

  • Oracle: the RULING comment on No address-taking metamorphic or opcode scan exists, so no verdict is bound to an account #83 (No address-taking metamorphic or opcode scan exists, so no verdict is bound to an account #83 (comment)), read independently of the implementation: the opcode-scan pair takes the metamorphic pair's boundary rule — address-taking entry points REVERT on a codeless account with CodelessAccount(address) per the E+D class from checkNotMetamorphic reports an account with no code as NOT metamorphic #55, before delegating to the bytes functions; library-only, IExtrospectV1 untouched, interface ruled separately. Expected revert data in tests is constructed from the ruling's required shape via abi.encodeWithSelector, not observed from the code under test.

  • Category check: the ruling asks for (1) scanEVMOpcodesPresentInBytecode(address) and scanEVMOpcodesReachableInBytecode(address) reverting CodelessAccount(address) on a codeless account — covered, src/lib/LibExtrospectBytecode.sol, revert and payload pinned; (2) delegation to the existing bytes functions — covered, pinned by the Selfdestruct-agreement, EOF and equivalence-fuzz tests; (3) library-only with IExtrospectV1 untouched, same as PR Reject codeless accounts in address-taking absence checks #136 — covered, no src/interface/ file in the diff; (4) PR Reject codeless accounts in address-taking absence checks #136's pattern for guard, error and test shapes — followed, including the failing-tests-first commit. Full suite: 398 passed, 3 failed — only the fork tests needing ARBITRUM_RPC_URL (environmental, fail on main too, The fork-test exclusion drops 7 tests to skip 3, leaving four non-fork checkCBORTrimmedBytecodeHash paths unmeasured #85). Nothing outside the ruled scope was changed.

🤖 Generated with Claude Code

baku-ccron and others added 2 commits August 19, 2026 13:53
Issue #83 (remaining half; ruling 2026-08-19): the opcode-scan pair takes
the same boundary rule as the metamorphic pair from PR #136 — the
address-taking entry points revert on a codeless account rather than
scan its empty code to a zero bitmap. This commit adds the
discriminating tests plus the minimal scaffold they need to compile:
`scanEVMOpcodesPresentInBytecode(address)` and
`scanEVMOpcodesReachableInBytecode(address)`, deliberately WITHOUT the
codeless guard, so the new tests fail behaviourally:

- testScanEVMOpcodesPresentAddressRevertsOnCodelessAccount
- testScanEVMOpcodesReachableAddressRevertsOnCodelessAccount

`nix develop -c forge test`: 396 passed, 5 failed — the 2 above plus the
3 fork tests needing ARBITRUM_RPC_URL (environmental, fail on main too).

Also pins for each address entry point: delegation agreement with the
bytes scan on a deployed SELFDESTRUCT contract, the
`EOFBytecodeNotSupported` revert on etched EOF code, and fuzzed
equivalence with the bytes scan over nonempty non-EOF etched code; those
pass already against the scaffold.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Issue #83 (remaining half; ruling 2026-08-19): the opcode-scan pair
takes the same boundary rule as the metamorphic pair from PR #136.
`scanEVMOpcodesPresentInBytecode(address)` and
`scanEVMOpcodesReachableInBytecode(address)` now revert with
`CodelessAccount` carrying the address when the account has no code,
before delegating to the bytes functions. An account with no code can
gain any code later — an unoccupied CREATE2 target, a self-destructed
account between incarnations, or an EOA that can gain code by EIP-7702
delegation — so a zero bitmap for it would vouch for nothing, and only
the address boundary has the information to refuse.

The bytes entry points are unchanged and stay total over bytes: empty
bytecode scans to a zero bitmap, pinned at both the empty-literal and
codeless-account readings since PR #136. Their NatSpec now
cross-references the address entry points as the way to bind a scan to
an account. README updated to match. `IExtrospectV1` untouched; the
interface surface for a new deploy is #139.

`nix develop -c forge test`: 398 passed, 3 failed — the 3 fork tests
needing ARBITRUM_RPC_URL (environmental, fail on main too). With
`--no-match-path "*fork*"`: 398 passed, 0 failed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Aug 19, 2026

Copy link
Copy Markdown

Warning

Review limit reached

@thedavidmeister, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 19 minutes

Limit details: You’ve used the included review currently available.

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits within each organization.

For paid Pro and Pro+ reviews, CodeRabbit uses a developer's included PR review attempts over the past 7 days to set the current hourly allowance. At typical activity levels, the full plan allowance applies. Higher sustained activity can lower the allowance until earlier attempts leave the 7-day window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: c8bda63f-a7c6-42a3-86d6-2f2710090e25

📥 Commits

Reviewing files that changed from the base of the PR and between 64cf664 and 4d8b502.

📒 Files selected for processing (4)
  • README.md
  • src/lib/LibExtrospectBytecode.sol
  • test/src/lib/LibExtrospectBytecode.scanEVMOpcodesPresentInBytecode.t.sol
  • test/src/lib/LibExtrospectBytecode.scanEVMOpcodesReachableInBytecode.t.sol

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@thedavidmeister
thedavidmeister merged commit 5f64d09 into main Aug 19, 2026
4 checks passed
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.

No address-taking metamorphic or opcode scan exists, so no verdict is bound to an account

1 participant