Title
Malformed EIP-7702 designator length is treated as delegated code in core logic
Summary
evmone core delegation detection currently matches only the 3-byte prefix 0xef0100, without requiring the full 23-byte designator length (0xef0100 || address).
As a result, code like 0xef0100 (3 bytes) or 0xef0100 || address || extra (24+ bytes) is treated as delegated by evmone, while geth/revm use strict 23-byte parsing for delegation indicators.
Why this matters
EIP-7702 defines a delegation indicator as 0xef0100 || address (exactly 23 bytes). Prefix-only matching can alter call routing and transaction validity checks for accounts whose code merely starts with that prefix.
This is likely not exploitable on live networks through normal transaction flow (malformed indicators are not valid EIP-7702 designators, and evmone t8n/statetest prestate validation rejects invalid lengths), but it is still an execution-consistency bug in core/runtime logic that can affect custom genesis/state import/blockchain-test contexts.
Affected runtime paths:
lib/evmone/instructions_calls.cpp calls get_delegate_address() during CALL-family execution.
test/state/state.cpp uses is_code_delegated() for EIP-3607 sender checks and authorization processing.
Root cause
evmone
lib/evmone/delegation.hpp
is_code_delegated(bytes_view code) returns code.starts_with(DELEGATION_MAGIC).
lib/evmone/delegation.cpp
get_delegate_address() accepts any prefix match and copies whatever bytes follow, assuming full size.
- In release builds, short/long designators are not rejected by this function.
geth
core/types/tx_setcode.go
ParseDelegation(b []byte) requires len(b) == 23 and prefix match.
revm
crates/bytecode/src/bytecode/mod.rs
new_eip7702_raw(bytes) rejects bytes.len() != 23 with InvalidLength.
eip7702_address() is only available for BytecodeKind::Eip7702.
Local reproductions
1) Direct core check in evmone
Build/run (from this session):
g++ -std=c++20 -I/home/chfast/proj/evmone -I/home/chfast/proj/evmone/evmc/include /tmp/check_delegate.cpp \
-L/home/chfast/proj/evmone/build/debug/lib -levmone \
-Wl,-rpath,/home/chfast/proj/evmone/build/debug/lib -o /tmp/check_delegate && /tmp/check_delegate
Observed:
short: delegated 0000000000000000000000000000000000000000
valid23: delegated 0000000000000000000000000000000000000011
long24: delegated 0000000000000000000000000000000000000011
Expected strict behavior: only valid23 should be delegated.
2) geth reference behavior
cd /tmp/checkgeth && go run .
Observed:
short: ok=false addr=0000000000000000000000000000000000000000
valid23: ok=true addr=0000000000000000000000000000000000000011
long24: ok=false addr=0000000000000000000000000000000000000000
3) t8n observable divergence (input handling)
Fixture in:
files/case22-prague-delegation-shortcode/
Running Prague t8n on that fixture:
- geth accepts and executes
- evmone-t8n rejects with:
EIP-7702 delegation designator at ... has invalid size
This confirms strict-length expectation exists in evmone test input validation, but core delegation helper itself still performs prefix-only matching.
4) Blockchain-test style semantic repro with malformed long designator
Fixture directory:
files/case23-prague-delegation-longcode/
- Delegated account code is malformed long form:
0xef0100 || 20-byte-target || 0x00 (24 bytes total).
- Target code does
SSTORE(0,1).
Reference transition (geth):
cd files/case23-prague-delegation-longcode
/home/chfast/go/bin/evm t8n \
--state.fork Prague --state.chainid 1 \
--input.alloc alloc.json --input.env env.json --input.txs txs.json \
--output.basedir out-geth --output.result result.json --output.alloc alloc.json
Observed in out-geth/result.json:
gasUsed = 0x186a0
stateRoot = 0x463f7388...45e2435
- receipt status
0x0 (invalid opcode semantics on 0xef... code)
evmone execution on equivalent block fixture:
/home/chfast/proj/evmone/build/debug/bin/evmone-blockchaintest \
files/case23-prague-delegation-longcode/blocktest_longcode.json
Observed:
- different
stateRoot = 0x7451ee8f...301421dd
- different
gas_used = 43106
- resulting state shows
0x095e...d87 storage slot 0x00 -> 0x01
This demonstrates runtime call-routing divergence from malformed long designators (not just parser-level rejection).
Suggested fix
Require exact length in core delegation detection:
- either change
is_code_delegated() to enforce size == 23 and prefix
- or add a separate strict helper for places that require a valid designator (CALL routing, sender validation, authorization checks)
and keep behavior consistent across:
lib/evmone/delegation.*
test/state/state.cpp
test/utils/statetest_loader.cpp
Title
Malformed EIP-7702 designator length is treated as delegated code in core logic
Summary
evmonecore delegation detection currently matches only the 3-byte prefix0xef0100, without requiring the full 23-byte designator length (0xef0100 || address).As a result, code like
0xef0100(3 bytes) or0xef0100 || address || extra(24+ bytes) is treated as delegated byevmone, while geth/revm use strict 23-byte parsing for delegation indicators.Why this matters
EIP-7702 defines a delegation indicator as
0xef0100 || address(exactly 23 bytes). Prefix-only matching can alter call routing and transaction validity checks for accounts whose code merely starts with that prefix.This is likely not exploitable on live networks through normal transaction flow (malformed indicators are not valid EIP-7702 designators, and evmone t8n/statetest prestate validation rejects invalid lengths), but it is still an execution-consistency bug in core/runtime logic that can affect custom genesis/state import/blockchain-test contexts.
Affected runtime paths:
lib/evmone/instructions_calls.cppcallsget_delegate_address()during CALL-family execution.test/state/state.cppusesis_code_delegated()for EIP-3607 sender checks and authorization processing.Root cause
evmone
lib/evmone/delegation.hppis_code_delegated(bytes_view code)returnscode.starts_with(DELEGATION_MAGIC).lib/evmone/delegation.cppget_delegate_address()accepts any prefix match and copies whatever bytes follow, assuming full size.geth
core/types/tx_setcode.goParseDelegation(b []byte)requireslen(b) == 23and prefix match.revm
crates/bytecode/src/bytecode/mod.rsnew_eip7702_raw(bytes)rejectsbytes.len() != 23withInvalidLength.eip7702_address()is only available forBytecodeKind::Eip7702.Local reproductions
1) Direct core check in evmone
Build/run (from this session):
g++ -std=c++20 -I/home/chfast/proj/evmone -I/home/chfast/proj/evmone/evmc/include /tmp/check_delegate.cpp \ -L/home/chfast/proj/evmone/build/debug/lib -levmone \ -Wl,-rpath,/home/chfast/proj/evmone/build/debug/lib -o /tmp/check_delegate && /tmp/check_delegateObserved:
Expected strict behavior: only
valid23should be delegated.2) geth reference behavior
Observed:
3) t8n observable divergence (input handling)
Fixture in:
files/case22-prague-delegation-shortcode/Running Prague t8n on that fixture:
EIP-7702 delegation designator at ... has invalid sizeThis confirms strict-length expectation exists in evmone test input validation, but core delegation helper itself still performs prefix-only matching.
4) Blockchain-test style semantic repro with malformed long designator
Fixture directory:
files/case23-prague-delegation-longcode/0xef0100 || 20-byte-target || 0x00(24 bytes total).SSTORE(0,1).Reference transition (geth):
cd files/case23-prague-delegation-longcode /home/chfast/go/bin/evm t8n \ --state.fork Prague --state.chainid 1 \ --input.alloc alloc.json --input.env env.json --input.txs txs.json \ --output.basedir out-geth --output.result result.json --output.alloc alloc.jsonObserved in
out-geth/result.json:gasUsed = 0x186a0stateRoot = 0x463f7388...45e24350x0(invalid opcode semantics on0xef...code)evmone execution on equivalent block fixture:
Observed:
stateRoot = 0x7451ee8f...301421ddgas_used = 431060x095e...d87storage slot0x00 -> 0x01This demonstrates runtime call-routing divergence from malformed long designators (not just parser-level rejection).
Suggested fix
Require exact length in core delegation detection:
is_code_delegated()to enforcesize == 23and prefixand keep behavior consistent across:
lib/evmone/delegation.*test/state/state.cpptest/utils/statetest_loader.cpp