Spec: https://github.com/ethereum/EIPs/blob/fad1a1acab0a433970e47e0b73a4f8a28c220675/EIPS/eip-7928.md (2026-02-03)
"In Ethereum we strive for a lean and easy to implement protocol. With your experiences from implementing core BAL parts gained in this chat - when analyzing the EIP spec in @EIPs/EIPS/eip-7928.md would you see potential enhancements or changes to the spec itself, which would significantly reduce implementation effort on the one hand and have no or only litte downsides (if present, please name them) on the other side?"
2026-02-05, Cursor, Sonnet 4.5
- Recommendations for Simplification
- 1. CRITICAL: Clarify Opcode Gas Charging vs. Gas Calculation
- 2. Add Concrete Implementation Pattern for Net-Zero Detection
- 3. Strengthen EIP-7702 Delegation Timing Language
- 4. Add "Checkpoint Preservation" Implementation Note
- 5. Clarify "Opcode Execution Function Runs" Timing
- 6. Simplify Net-Zero Balance Requirement (Alternative Approach)
- Summary Table (Updated Spec)
- Top 3 Recommendations for Maximum Impact
- Overall Assessment
Current Gap (Lines 114-124): The spec defines "pre-state validation" costs but doesn't explicitly state when in the execution flow to record addresses relative to opcode gas charging.
Problem we encountered: Modern EVM implementations have this flow:
- Gas calculation function runs (computes
gascost) - Gas calculation returns
- Interpreter charges
gasfrom available gas ← Can OOG here! - Opcode execution function runs
The spec's "pre-state validation" table helps, but doesn't clarify if addresses should be recorded:
- Before step 3 (after gas calc returns but before charging)? → Causes EXTRA addresses
- After step 3 (after gas charged but before execution)? → Works perfectly BUT never runs if step 3 OOGs
Recommendation: Add to section 114 (Gas Validation Before State Access):
#### Opcode Gas Charging Sequence
For state-accessing opcodes, implementations typically:
1. Calculate gas cost (pre-state validation)
2. Return calculated cost to interpreter
3. Interpreter charges gas from available gas ← OOG can occur here
4. Execute opcode function (performs actual state access)
For BAL inclusion:
- Addresses/slots MUST be recorded in step 4 (after gas charging succeeds)
- If OOG occurs in step 3, the target MUST NOT be included
- Exception: Once a Message is created and execution begins, all touched
addresses MUST be preserved even if subsequent OOG occurs (per "Exceptional
halts" rule)Impact:
- ✅ Benefit: Eliminates the architectural ambiguity we spent hours debugging
- ✅ Benefit: Makes it clear opcode execution functions (not gas functions) are the recording point
- ❌ Downside: Adds implementation details to spec
- Assessment: Worth it - this is the #1 implementation trap
Current Requirement (Lines 209, 192):
"If an account's balance changes during a transaction, but its post-transaction balance is equal to its pre-transaction balance, then the change MUST NOT be recorded"
"Implementations MUST check the pre-transaction value to correctly distinguish between actual writes and no-op writes"
Problem: The spec requires the logic but doesn't suggest how to track pre-transaction values efficiently.
Recommendation: Add subsection under "Recording Semantics by Change Type":
#### Implementation Pattern: Original Value Tracking
For net-zero detection (balance and storage), implementations SHOULD:
1. Maintain a separate map of original values (pre-transaction state)
2. Capture original value on FIRST modification attempt, not at tx start
3. Use "first-write-wins" semantics for the original value map
4. After transaction completes, compare final vs. original:
- If equal: Remove from changes (convert storage to read if needed)
- If different: Keep in changes
Example: If balance goes 100 → 50 → 100 during a transaction:
- First modification captures original=100
- Final balance=100
- Net-zero detected, remove from balance_changesImpact:
- ✅ Benefit: Provides clear implementation path, reduces guesswork by ~30%
- ✅ Benefit: Prevents inefficient "track everything at tx start" approaches
- ❌ Downside: Non-normative guidance in normative section (but clearly marked as SHOULD)
- Assessment: High value, low cost
Current (Line 244):
"The delegation target MUST NOT be included during delegation creation or modification and MUST only be included once it is actually loaded as an execution target"
Remaining Ambiguity: "Loaded as an execution target" is vague. We spent time determining if this means:
- When delegation is checked during gas calculation?
- When delegation code is fetched?
- When Message is created with delegated codeAddress?
Recommendation: Replace with:
The delegation target MUST be included when:
1. A Message object is created with the delegation target as its codeAddress, OR
2. The delegation target's code is loaded via _loadCode() or equivalent
during message preparation
The delegation target MUST NOT be included merely because:
- Delegation authorization was validated
- Delegation target address was read from delegating account's code field
- Without subsequent execution of the delegated codeImpact:
- ✅ Benefit: Crystal clear boundary (Message creation = include)
- ✅ Benefit: Aligns with natural implementation checkpoints
- ❌ Downside: None
- Assessment: Clean win
Current Gap: The spec mentions exceptional halts preserve addresses (line 241) but doesn't explain how this interacts with typical checkpoint/revert mechanisms.
Problem we encountered:
We initially thought addresses added after a checkpoint would be lost on revert. We had to implement special revert() logic to preserve them.
Recommendation: Add new subsection to "Gas Validation Before State Access":
#### Checkpoint and Revert Behavior
Most implementations use checkpoint/revert mechanisms for state rollback.
For BAL correctness:
**Address Touches (Preserved on Revert):**
- When a checkpoint reverts, touched addresses MUST remain in the BAL
- If no state changes survive the revert, addresses appear with empty lists
- Storage slots accessed during reverted execution MUST appear in storage_reads
**State Changes (Discarded on Revert):**
- Balance/nonce/code/storage changes from reverted execution are discarded
- Only the final post-transaction values are recorded
**Implementation Pattern:**
```python
def revert_checkpoint():
# Restore state to checkpoint
state = checkpoint_stack.pop()
# Preserve addresses but discard their changes
for addr in current_accesses:
if addr not in checkpoint_accesses:
checkpoint_accesses[addr] = empty_changes()
# Merge storage reads (including reverted writes)
checkpoint_accesses[addr].storage_reads |= current_accesses[addr].storage_reads
checkpoint_accesses[addr].storage_reads |= current_accesses[addr].storage_writes.keys()
**Impact:**
- ✅ **Benefit**: Would have saved us 3-4 hours of debugging the `revert()` logic
- ✅ **Benefit**: Makes "exceptional halts" requirement implementable
- ❌ **Downside**: Adds implementation pseudocode (non-normative)
- **Assessment**: High value for implementers
---
### **5. Clarify "Opcode Execution Function Runs" Timing**
**Current (Line 121-123):**
> "Once pre-state validation passes, the target is accessed and included in the BAL."
**Remaining Issue from Our Implementation:**
This statement is **technically incorrect** for our architecture and likely others:
**Actual execution flow:**
1. Pre-state validation runs → passes
2. Gas calculation function **returns gas cost**
3. **Interpreter charges the gas** ← Can still OOG here!
4. **Only NOW** does "target is accessed" happen (opcode function runs)
The spec says "once pre-state validation passes, target is accessed" but there's a gap between validation passing and actual access.
**Recommendation:**
Refine lines 121-123 to:
```markdown
Once pre-state validation passes, the target WILL BE accessed and included
in the BAL, provided the interpreter successfully charges the opcode gas.
If out-of-gas occurs when the interpreter attempts to charge the calculated
gas cost (after pre-state validation completes but before the opcode
executes), the target MUST NOT be included.
Once the opcode execution function begins (gas successfully charged), the
target MUST be included in the BAL even if subsequent out-of-gas occurs
during execution.
Impact:
- ✅ Benefit: Closes the timing gap we discovered
- ✅ Benefit: Makes spec architecturally accurate
- ❌ Downside: More verbose
- Assessment: Critical for correctness
Current (Lines 207-216): Requires net-zero detection for balances with multiple edge cases.
Our Experience: This added significant complexity:
originalBalancesMap infrastructure- API changes to
addBalanceChange() - Special handling in multiple locations
- ~20% of implementation effort
Alternative Recommendation: Option A (Status Quo): Keep requirement, add implementation note (covered in #2)
Option B (Radical Simplification): Make net-zero detection OPTIONAL:
If an account's balance changes during a transaction, but its post-transaction
balance equals its pre-transaction balance:
- Implementations MAY omit the balance_change (recommended for size optimization)
- Implementations MAY include the balance_change (simpler implementation)
The sender and recipient addresses MUST still be included in AccountChanges
regardless of which approach is chosen.Impact:
- ✅ Benefit: Reduces implementation complexity by ~20%
- ✅ Benefit: Allows simpler implementations to skip this optimization
- ❌ Downside: Non-deterministic BAL size (implementations could differ)
- ❌ Downside: Parallel execution might need to handle both formats
- Assessment: Only viable if BAL format variance is acceptable
| Recommendation | Implementation Effort Reduction | Spec Impact | Priority |
|---|---|---|---|
| 1. Opcode Gas Charging Timing | ⭐⭐⭐⭐⭐ ~40% | +8 lines (clarification) | CRITICAL |
| 2. Net-Zero Implementation Pattern | ⭐⭐⭐ ~15% | +12 lines (guidance) | High |
| 3. EIP-7702 Delegation Boundary | ⭐⭐⭐ ~15% | +5 lines (clarification) | High |
| 4. Checkpoint/Revert Guidance | ⭐⭐⭐⭐ ~25% | +20 lines (pseudocode) | High |
| 5. Clarify "Target Accessed" Timing | ⭐⭐⭐⭐ ~30% | +4 lines (precision) | High |
| 6. Make Net-Zero Optional (B) | ⭐⭐⭐⭐⭐ ~20% | +3 lines, breaks determinism | Low (controversial) |
Must-have. Closes the critical gap between gas calculation and gas charging. This is where we lost the most debugging time.
High value. The "exceptional halts preserve addresses" requirement is impossible to implement correctly without understanding how to preserve addresses across reverts.
High value. Fixes technical inaccuracy in current spec (line 121) that caused confusion about when recording should happen.
The updated spec is significantly better! The "Gas Validation Before State Access" section addresses ~50% of the ambiguities we encountered. The remaining recommendations would:
- Combined benefit: Reduce implementation effort by additional ~40-50%
- Combined cost: ~30 lines of additional spec text (mostly non-normative guidance)
- Trade-off: Strongly positive - this EIP is complex enough that extra guidance is justified
The biggest remaining gap is Recommendation #1 (opcode gas charging timing), which would have saved us several hours of trial-and-error during the OOG debugging session we just completed.