Repository navigation
Address-taking opcode scans reject codeless accounts - #141
Conversation
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>
|
Warning Review limit reached
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 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 configurationConfiguration used: Organization UI Review profile: ASSERTIVE Plan: Pro Plus Run ID: 📒 Files selected for processing (4)
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. Comment |
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)andscanEVMOpcodesReachableInBytecode(address)entry points revert withCodelessAccount(address)(the E+D class from #55) on a codeless account, before delegating to the existing bytes functions. Library-only:IExtrospectV1stays untouched, same as PR #136 — the interface surface for a new deploy is ruled separately (#139).What changed
LibExtrospectBytecodegains the address-taking entry pointsscanEVMOpcodesPresentInBytecode(address)andscanEVMOpcodesReachableInBytecode(address): both read the account's code, revertCodelessAccount(account)when there is none, and delegate to the bytes functions otherwise. An account with no code can gain any code later — an unoccupiedCREATE2target, 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 explicitreturn, per org convention.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.IExtrospectV1untouched — no function or error added to or removed from the interface;git diff maintouches no file undersrc/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 revertsCodelessAccount(0xC2)from both address entry points, expected revert data built withabi.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.test*AddressSelfdestruct(SELFDESTRUCT bit set and exact agreement with the bytes scan over a deployed contract's code),test*AddressRevertsOnEOF(EOFBytecodeNotSupportedthrough the delegate on etched EOF code),test*AddressEquivalenceFuzz(address vs bytes agreement over nonempty non-EOF etched code, viaLibExtrospectTestEtch.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):src, then reverted)scanEVMOpcodesPresentInBytecode(address): codeless guard deletedtestScanEVMOpcodesPresentAddressRevertsOnCodelessAccount("next call did not revert as expected"); 397 passed, 1 failedscanEVMOpcodesReachableInBytecode(address): codeless guard deletedtestScanEVMOpcodesReachableAddressRevertsOnCodelessAccount(same shape); 397 passed, 1 failedCodelessAccount(address(0))instead of the accountCodelessAccount(0x0000...0000) != CodelessAccount(0x0...00c2)— the error's address payload is pinned; 397 passed, 1 failedCodelessAccount(address(0))instead of the accounttestScanEVMOpcodesPresentAddressEquivalenceFuzz(counterexample where reachable != present); 397 passed, 1 failedtestScanEVMOpcodesReachableAddressEquivalenceFuzz(counterexampleargs=[0x...079f]); 397 passed, 1 failedbytecode.length == 0->== 1testScanEVMOpcodesPresentAddressRevertsOnCodelessAccount(length 0 no longer reverts) andtestScanEVMOpcodesPresentAddressEquivalenceFuzzwith a 1-byte counterexample (args=[0x32]revertingCodelessAccount(0xbEEF)); 396 passed, 2 failedbytecode.length == 0->== 1args=[0x49]); 396 passed, 2 failedAll 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,IExtrospectV1untouched, interface ruled separately. Expected revert data in tests is constructed from the ruling's required shape viaabi.encodeWithSelector, not observed from the code under test.Category check: the ruling asks for (1)
scanEVMOpcodesPresentInBytecode(address)andscanEVMOpcodesReachableInBytecode(address)revertingCodelessAccount(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 withIExtrospectV1untouched, same as PR Reject codeless accounts in address-taking absence checks #136 — covered, nosrc/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 needingARBITRUM_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