Commit e913d87
perf(evm): tighten CALL hot path; fix EIP-7702 delegation-to-precompile semantics (#11547)
* perf(evm): tighten CALL hot path - delegation, gas clamp, allocation
InstructionCall: discover the EIP-7702 delegation target inline with the
initial GetCachedCodeInfo(followDelegation: false) call instead of doing a
separate TryGetDelegation followed by a second GetCachedCodeInfo. The cold
access charge for the delegated address still lands between the two reads
so gas accounting is unchanged; one fewer state read in the common case.
InstructionCall: replace UInt256.Min((UInt256)cap, gasLimit) plus the
256-bit >= long.MaxValue check plus (long)gasLimit with a scalar
IsUint64 + u0 compare. The 63/64 cap is a non-negative long, so the
clamped result is guaranteed to fit without any 256-bit math. Same
numerical result; no 4-limb work on every CALL/CALLCODE/DELEGATECALL/
STATICCALL.
ICodeInfoRepository.TryGetDelegatedAddress: drop the ToArray() and use
the Address(ReadOnlySpan<byte>) constructor directly. Removes one
byte[20] allocation per successful 7702 delegation parse.
StateOverridesExtensions.UpdateCode: precompile check now passes
followDelegation: false so the question matches what the override system
is actually asking (is THIS account a precompile, not where it delegates).
Tests: 480/480 pass in Nethermind.Evm.Test under filter Call|Gas|VM
(2 pre-existing skips for issue #140).
* refactor(evm): post-review cleanup pass on CALL hot path
- Add GetCachedCodeInfoNoDelegation(repo, addr, spec) extension to
ICodeInfoRepository so callers that explicitly do not want delegation
resolution can drop the (followDelegation: false, ..., out _) ceremony.
- Use the new extension at the second GetCachedCodeInfo call in
InstructionCall and at StateOverridesExtensions.UpdateCode.
- Add a one-line WHY comment above the second InstructionCall lookup
explaining the EIP-7702 ordering constraint (delegated code must be
loaded after charging cold-access gas for the delegation target).
- Add Debug.Assert(gasAvailable >= 0) above the (ulong)cap cast in the
63/64 gas clamp; documents the invariant the cast relies on.
* fix(evm): record delegated precompile address for EIP-7928 BAL
When a 7702 EOA delegates to a precompile address and the
PrecompileCachedCodeInfoRepository decorator is installed in the
processing pipeline, the second GetCachedCodeInfoNoDelegation lookup
in InstructionCall hits the decorator's cached precompile fast-path
which returns without going through the world state. The BlockAccessList
generated for EIP-7928 therefore omits the delegation target.
The base CodeInfoRepository compensates by calling AddAccountRead on
the precompile fast-path; the decorator does not, and other call sites
that pass precompile addresses to GetCachedCodeInfo (EXTCODECOPY,
EXTCODESIZE) already do AddAccountRead at the call site. Add the
matching call before the second InstructionCall lookup.
Pre-this-branch the read was incidentally captured because the
followDelegation: true path routed through InternalGetCodeInfo ->
GetCodeHash -> AddAccountRead. The CALL hot-path refactor on this
branch switched to followDelegation: false + an explicit second fetch,
which bypasses that chain when the target is a precompile.
AddAccountRead on IWorldState is a default-interface no-op for plain
world states and idempotent on TracedAccessWorldState (BlockAccessList
guards with ContainsKey), so the call is safe in all configurations.
Adds a regression test wiring PrecompileCachedCodeInfoRepository into
the Eip7928Tests fixture and asserting the delegated Sha256 precompile
address appears in the generated BAL. Verified: test FAILS without the
fix (2/2 across both parallel modes), PASSES with the fix.
* fix
* test(evm): regression for EIP-7702 delegation-to-precompile FastCall
Adds Calling_account_delegated_to_precompile_uses_FastCall_per_EIP_7702 -
a unit-level regression test that specifically catches the spec violation
fixed in 3b82033. Uses 0 inner gas to discriminate: real precompile
execution OOGs and pushes 0; FastCall pushes 1 regardless of forwarded gas.
Verified: test fails 2/2 (both parallel modes) without the spec fix,
passes 2/2 with it.
Also drops the now-stale "schedule background analysis if needed" comment
above the codeSource GetCachedCodeInfo call (PR feedback): the phrase
described the previous single-call followDelegation:true pattern and is
misleading after the followDelegation:false split. The named argument
followDelegation:false is self-documenting.
* docs(evm): tighten comments per AGENTS.md WHY-only rule
Three multi-line comments around the InstructionCall delegation block and
two test XML <remarks> blocks were paragraph-style explanations. AGENTS.md
asks for one-liners that capture the WHY (the EIP, the non-obvious choice)
and nothing more. Compressed each to one or two lines while preserving the
EIP citations that explain the corner case.
Net -23 lines.
* fix(evm): record code reads in BAL on PrecompileCachedCodeInfoRepository fast-path
The decorator's IsPrecompile fast-path returned the cached precompile CodeInfo
without calling AddAccountRead, breaking the contract that the base
CodeInfoRepository upholds at CodeInfoRepository.cs:48 (where the precompile
case explicitly records the read for EIP-7928 BAL inclusion).
For most CALL-family opcodes the gap was masked because target == codeSource
and the subsequent state.AccountExists(target) records indirectly via
TracedAccessWorldState. But DELEGATECALL/CALLCODE set target = ExecutingAccount
(not codeSource), so when codeSource is a precompile and the decorator's
fast-path fires, the precompile address is missing from the generated BAL.
Inject IWorldState into the decorator and add worldState.AddAccountRead on the
fast-path; mirrors the base impl. Safe in all configurations: no-op default-
interface impl on plain world states, idempotent on TracedAccessWorldState.
Adds two regression tests in Eip7928Tests:
- DelegateCall_to_precompile_records_codeSource_in_BAL_*: catches the real
gap. Verified failing 2/2 without the fix, passing 2/2 with it.
- Direct_transaction_to_precompile_records_recipient_in_BAL_*: documents that
tx.to == precompile already records the recipient correctly via an existing
TransactionProcessor.Execute path (passing 2/2 both before and after; not a
gap, just a positive invariant).
Updates 14 decorator-construction sites in PrecompileCachedCodeInfoRepository
unit tests to pass Substitute.For<IWorldState>(); they assert caching, not
recording.
pyspec test_pointer_to_precompile: 1068/1068 pass before and after.
Nethermind.Evm.Test (Call|Gas|VM|Eip7928|Eip7702): 622/622 pass.
PrecompileCachedCodeInfoRepository unit tests: 14/14 pass.
* test(evm): de-duplicate Eip7928Tests helpers
Per AGENTS.md test guidelines (factor shared setup into helpers):
- Merge InitWorldStateWithPrecompileDelegation into InitWorldState via a
new optional delegationTarget parameter. Single helper now handles both
the AddressD->code intermediate-hop case and direct delegation to e.g.
a precompile.
- Merge CreateTracedProcessorWithPrecompileCache into CreateTracedProcessor
via a wrapPrecompileCache flag.
- Extract BuildContractTx for the recurring templateTx + IntrinsicGasCalculator
+ signed-tx dance. Applied to all five call sites (the four new precompile
tests plus the two pre-existing Constructs_BAL_when_processing_code* tests).
No behavior change; all 142 Eip7928 tests (parallel=false and parallel=true
fixtures) still pass. Net -83 lines.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* test(evm): drop XML docs from dedupe helpers to match file style
Surrounding test helpers in Eip7928Tests.cs (and the rest of the file)
have no /// blocks — names self-document. Removes the doc additions from
the prior dedupe commit (BuildContractTx, InitWorldState, and the
wrapPrecompileCache <remarks> on CreateTracedProcessor).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: lukasz.rozmej <lukasz.rozmej@gmail.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>1 parent 67c0da6 commit e913d87
7 files changed
Lines changed: 229 additions & 61 deletions
File tree
- src/Nethermind
- Nethermind.Blockchain.Test
- Nethermind.Blockchain
- Nethermind.Evm.Test
- Nethermind.Evm
- Instructions
- Nethermind.Init/Modules
Lines changed: 14 additions & 14 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
51 | 51 | | |
52 | 52 | | |
53 | 53 | | |
54 | | - | |
| 54 | + | |
55 | 55 | | |
56 | 56 | | |
57 | 57 | | |
| |||
81 | 81 | | |
82 | 82 | | |
83 | 83 | | |
84 | | - | |
| 84 | + | |
85 | 85 | | |
86 | 86 | | |
87 | 87 | | |
| |||
107 | 107 | | |
108 | 108 | | |
109 | 109 | | |
110 | | - | |
| 110 | + | |
111 | 111 | | |
112 | 112 | | |
113 | 113 | | |
| |||
136 | 136 | | |
137 | 137 | | |
138 | 138 | | |
139 | | - | |
| 139 | + | |
140 | 140 | | |
141 | 141 | | |
142 | 142 | | |
| |||
171 | 171 | | |
172 | 172 | | |
173 | 173 | | |
174 | | - | |
| 174 | + | |
175 | 175 | | |
176 | 176 | | |
177 | 177 | | |
| |||
205 | 205 | | |
206 | 206 | | |
207 | 207 | | |
208 | | - | |
| 208 | + | |
209 | 209 | | |
210 | 210 | | |
211 | 211 | | |
| |||
231 | 231 | | |
232 | 232 | | |
233 | 233 | | |
234 | | - | |
| 234 | + | |
235 | 235 | | |
236 | 236 | | |
237 | 237 | | |
| |||
264 | 264 | | |
265 | 265 | | |
266 | 266 | | |
267 | | - | |
| 267 | + | |
268 | 268 | | |
269 | 269 | | |
270 | 270 | | |
| |||
296 | 296 | | |
297 | 297 | | |
298 | 298 | | |
299 | | - | |
| 299 | + | |
300 | 300 | | |
301 | 301 | | |
302 | 302 | | |
| |||
335 | 335 | | |
336 | 336 | | |
337 | 337 | | |
338 | | - | |
| 338 | + | |
339 | 339 | | |
340 | 340 | | |
341 | 341 | | |
| |||
369 | 369 | | |
370 | 370 | | |
371 | 371 | | |
372 | | - | |
| 372 | + | |
373 | 373 | | |
374 | 374 | | |
375 | 375 | | |
| |||
402 | 402 | | |
403 | 403 | | |
404 | 404 | | |
405 | | - | |
| 405 | + | |
406 | 406 | | |
407 | 407 | | |
408 | 408 | | |
| |||
439 | 439 | | |
440 | 440 | | |
441 | 441 | | |
442 | | - | |
| 442 | + | |
443 | 443 | | |
444 | 444 | | |
445 | 445 | | |
| |||
477 | 477 | | |
478 | 478 | | |
479 | 479 | | |
480 | | - | |
| 480 | + | |
481 | 481 | | |
482 | 482 | | |
483 | 483 | | |
| |||
Lines changed: 3 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
16 | 16 | | |
17 | 17 | | |
18 | 18 | | |
| 19 | + | |
19 | 20 | | |
20 | 21 | | |
21 | 22 | | |
| |||
29 | 30 | | |
30 | 31 | | |
31 | 32 | | |
| 33 | + | |
| 34 | + | |
32 | 35 | | |
33 | 36 | | |
34 | 37 | | |
| |||
0 commit comments