Skip to content

Commit ec5651e

Browse files
committed
fix more ef tests
1 parent 6c2a48b commit ec5651e

4 files changed

Lines changed: 191 additions & 183 deletions

File tree

crates/common/types/block_access_list.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -588,6 +588,15 @@ impl BlockAccessListRecorder {
588588
}
589589
}
590590

591+
// If this slot was promoted from read to write, undo the promotion
592+
// so build() doesn't skip it from storage_reads.
593+
if let Some(promoted) = self.reads_promoted_to_writes.get_mut(&addr) {
594+
promoted.retain(|s| *s != slot);
595+
if promoted.is_empty() {
596+
self.reads_promoted_to_writes.remove(&addr);
597+
}
598+
}
599+
591600
// Add as a read instead
592601
self.storage_reads.entry(addr).or_default().insert(slot);
593602
}

crates/vm/levm/src/hooks/default_hook.rs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -182,9 +182,11 @@ pub fn refund_sender(
182182

183183
// EIP-7778: Separate block vs user gas accounting for Amsterdam+
184184
if vm.env.config.fork >= Fork::Amsterdam {
185-
// Block accounting uses pre-refund gas
186-
ctx_result.gas_used = gas_used_pre_refund;
187-
// User pays post-refund gas
185+
// Block accounting uses max(pre-refund gas, calldata floor)
186+
// This prevents gas smuggling via refunds (EIP-7778)
187+
let floor = vm.get_min_gas_used()?;
188+
ctx_result.gas_used = gas_used_pre_refund.max(floor);
189+
// User pays post-refund gas (with floor)
188190
ctx_result.gas_spent = gas_spent;
189191
} else {
190192
// Pre-Amsterdam: both use post-refund value

crates/vm/levm/src/opcode_handlers/system.rs

Lines changed: 165 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ use crate::{
33
constants::{FAIL, INIT_CODE_MAX_SIZE, SUCCESS},
44
errors::{ContextResult, ExceptionalHalt, InternalError, OpcodeResult, TxResult, VMError},
55
gas_cost::{self, max_message_call_gas},
6-
memory::calculate_memory_size,
6+
memory::{self, calculate_memory_size},
77
precompiles,
88
utils::{
99
address_to_word, create_eth_transfer_log, create_selfdestruct_log, word_to_address, *,
@@ -80,6 +80,53 @@ impl<'a> VM<'a> {
8080
callee,
8181
)?;
8282

83+
// Record addresses for BAL per EIP-7928, gated on intermediate gas checks
84+
// matching the reference: check_gas(access+transfer+memory) before callee,
85+
// check_gas(delegation+memory) before delegation target.
86+
if self.db.bal_recorder.is_some() {
87+
let mem_cost =
88+
memory::expansion_cost(new_memory_size, current_memory_size).unwrap_or(u64::MAX);
89+
let access_cost = if address_was_cold {
90+
gas_cost::COLD_ADDRESS_ACCESS_COST
91+
} else {
92+
gas_cost::WARM_ADDRESS_ACCESS_COST
93+
};
94+
let value_cost = if !value.is_zero() {
95+
gas_cost::CALL_POSITIVE_VALUE
96+
} else {
97+
0
98+
};
99+
let basic_cost = mem_cost.saturating_add(access_cost).saturating_add(value_cost);
100+
let gas_remaining = self.current_call_frame.gas_remaining;
101+
102+
if gas_remaining >= basic_cost as i64 {
103+
self.db
104+
.bal_recorder
105+
.as_mut()
106+
.unwrap()
107+
.record_touched_address(callee);
108+
109+
if is_delegation_7702 {
110+
// Reference check 2: gas >= access + transfer + create + delegation + memory
111+
let create_gas_cost = if account_is_empty && !value.is_zero() {
112+
gas_cost::CALL_TO_EMPTY_ACCOUNT
113+
} else {
114+
0
115+
};
116+
let delegation_check = basic_cost
117+
.saturating_add(create_gas_cost)
118+
.saturating_add(eip7702_gas_consumed);
119+
if gas_remaining >= delegation_check as i64 {
120+
self.db
121+
.bal_recorder
122+
.as_mut()
123+
.unwrap()
124+
.record_touched_address(code_address);
125+
}
126+
}
127+
}
128+
}
129+
83130
let (cost, gas_limit) = gas_cost::call(
84131
new_memory_size,
85132
current_memory_size,
@@ -96,19 +143,8 @@ impl<'a> VM<'a> {
96143
.ok_or(ExceptionalHalt::OutOfGas)?,
97144
)?;
98145

99-
// Make sure we have enough memory to write the return data
100-
// This is also needed to make sure we expand the memory even in cases where we don't have return data (such as transfers)
101146
callframe.memory.resize(new_memory_size)?;
102147

103-
// Record address touch for BAL (after gas checks pass per EIP-7928)
104-
if let Some(recorder) = self.db.bal_recorder.as_mut() {
105-
recorder.record_touched_address(callee);
106-
// If EIP-7702 delegation, also record the delegation target (code source)
107-
if is_delegation_7702 {
108-
recorder.record_touched_address(code_address);
109-
}
110-
}
111-
112148
// OPERATION
113149
let from = callframe.to; // The new sender will be the current contract.
114150
let to = callee; // Sub-context account. Note: code_address may differ if EIP-7702 delegation is active.
@@ -176,6 +212,7 @@ impl<'a> VM<'a> {
176212
// CHECK EIP7702
177213
let (is_delegation_7702, eip7702_gas_consumed, code_address, bytecode) =
178214
eip7702_get_code(self.db, &mut self.substate, address)?;
215+
179216
// GAS
180217
let (new_memory_size, gas_left, _account_is_empty, address_was_cold) = self
181218
.get_call_gas_params(
@@ -187,6 +224,45 @@ impl<'a> VM<'a> {
187224
address,
188225
)?;
189226

227+
// Record addresses for BAL per EIP-7928, gated on intermediate gas checks
228+
if self.db.bal_recorder.is_some() {
229+
let mem_cost =
230+
memory::expansion_cost(new_memory_size, current_memory_size).unwrap_or(u64::MAX);
231+
let access_cost = if address_was_cold {
232+
gas_cost::COLD_ADDRESS_ACCESS_COST
233+
} else {
234+
gas_cost::WARM_ADDRESS_ACCESS_COST
235+
};
236+
let value_cost = if !value.is_zero() {
237+
gas_cost::CALLCODE_POSITIVE_VALUE
238+
} else {
239+
0
240+
};
241+
let basic_cost = mem_cost.saturating_add(access_cost).saturating_add(value_cost);
242+
let gas_remaining = self.current_call_frame.gas_remaining;
243+
244+
if gas_remaining >= basic_cost as i64 {
245+
self.db
246+
.bal_recorder
247+
.as_mut()
248+
.unwrap()
249+
.record_touched_address(address);
250+
251+
if is_delegation_7702 {
252+
// Reference check 2: gas >= access + transfer + delegation + memory
253+
let delegation_check =
254+
basic_cost.saturating_add(eip7702_gas_consumed);
255+
if gas_remaining >= delegation_check as i64 {
256+
self.db
257+
.bal_recorder
258+
.as_mut()
259+
.unwrap()
260+
.record_touched_address(code_address);
261+
}
262+
}
263+
}
264+
}
265+
190266
let (cost, gas_limit) = gas_cost::callcode(
191267
new_memory_size,
192268
current_memory_size,
@@ -202,19 +278,8 @@ impl<'a> VM<'a> {
202278
.ok_or(ExceptionalHalt::OutOfGas)?,
203279
)?;
204280

205-
// Make sure we have enough memory to write the return data
206-
// This is also needed to make sure we expand the memory even in cases where we don't have return data (such as transfers)
207281
callframe.memory.resize(new_memory_size)?;
208282

209-
// Record address touch for BAL (after gas checks pass per EIP-7928)
210-
if let Some(recorder) = self.db.bal_recorder.as_mut() {
211-
recorder.record_touched_address(address);
212-
// If EIP-7702 delegation, also record the delegation target (code source)
213-
if is_delegation_7702 {
214-
recorder.record_touched_address(code_address);
215-
}
216-
}
217-
218283
// Sender and recipient are the same in this case. But the code executed is from another account.
219284
let from = callframe.to;
220285
let to = callframe.to;
@@ -313,6 +378,40 @@ impl<'a> VM<'a> {
313378
address,
314379
)?;
315380

381+
// Record addresses for BAL per EIP-7928, gated on intermediate gas checks
382+
if self.db.bal_recorder.is_some() {
383+
let mem_cost =
384+
memory::expansion_cost(new_memory_size, current_memory_size).unwrap_or(u64::MAX);
385+
let access_cost = if address_was_cold {
386+
gas_cost::COLD_ADDRESS_ACCESS_COST
387+
} else {
388+
gas_cost::WARM_ADDRESS_ACCESS_COST
389+
};
390+
let basic_cost = mem_cost.saturating_add(access_cost);
391+
let gas_remaining = self.current_call_frame.gas_remaining;
392+
393+
if gas_remaining >= basic_cost as i64 {
394+
self.db
395+
.bal_recorder
396+
.as_mut()
397+
.unwrap()
398+
.record_touched_address(address);
399+
400+
if is_delegation_7702 {
401+
// Reference check 2: gas >= access + delegation + memory
402+
let delegation_check =
403+
basic_cost.saturating_add(eip7702_gas_consumed);
404+
if gas_remaining >= delegation_check as i64 {
405+
self.db
406+
.bal_recorder
407+
.as_mut()
408+
.unwrap()
409+
.record_touched_address(code_address);
410+
}
411+
}
412+
}
413+
}
414+
316415
let (cost, gas_limit) = gas_cost::delegatecall(
317416
new_memory_size,
318417
current_memory_size,
@@ -327,19 +426,8 @@ impl<'a> VM<'a> {
327426
.ok_or(ExceptionalHalt::OutOfGas)?,
328427
)?;
329428

330-
// Make sure we have enough memory to write the return data
331-
// This is also needed to make sure we expand the memory even in cases where we don't have return data (such as transfers)
332429
callframe.memory.resize(new_memory_size)?;
333430

334-
// Record address touch for BAL (after gas checks pass per EIP-7928)
335-
if let Some(recorder) = self.db.bal_recorder.as_mut() {
336-
recorder.record_touched_address(address);
337-
// If EIP-7702 delegation, also record the delegation target (code source)
338-
if is_delegation_7702 {
339-
recorder.record_touched_address(code_address);
340-
}
341-
}
342-
343431
// OPERATION
344432
let from = callframe.msg_sender;
345433
let value = callframe.msg_value;
@@ -419,6 +507,40 @@ impl<'a> VM<'a> {
419507
address,
420508
)?;
421509

510+
// Record addresses for BAL per EIP-7928, gated on intermediate gas checks
511+
if self.db.bal_recorder.is_some() {
512+
let mem_cost =
513+
memory::expansion_cost(new_memory_size, current_memory_size).unwrap_or(u64::MAX);
514+
let access_cost = if address_was_cold {
515+
gas_cost::COLD_ADDRESS_ACCESS_COST
516+
} else {
517+
gas_cost::WARM_ADDRESS_ACCESS_COST
518+
};
519+
let basic_cost = mem_cost.saturating_add(access_cost);
520+
let gas_remaining = self.current_call_frame.gas_remaining;
521+
522+
if gas_remaining >= basic_cost as i64 {
523+
self.db
524+
.bal_recorder
525+
.as_mut()
526+
.unwrap()
527+
.record_touched_address(address);
528+
529+
if is_delegation_7702 {
530+
// Reference check 2: gas >= access + delegation + memory
531+
let delegation_check =
532+
basic_cost.saturating_add(eip7702_gas_consumed);
533+
if gas_remaining >= delegation_check as i64 {
534+
self.db
535+
.bal_recorder
536+
.as_mut()
537+
.unwrap()
538+
.record_touched_address(code_address);
539+
}
540+
}
541+
}
542+
}
543+
422544
let (cost, gas_limit) = gas_cost::staticcall(
423545
new_memory_size,
424546
current_memory_size,
@@ -433,19 +555,8 @@ impl<'a> VM<'a> {
433555
.ok_or(ExceptionalHalt::OutOfGas)?,
434556
)?;
435557

436-
// Make sure we have enough memory to write the return data
437-
// This is also needed to make sure we expand the memory even in cases where we don't have return data (such as transfers)
438558
callframe.memory.resize(new_memory_size)?;
439559

440-
// Record address touch for BAL (after gas checks pass per EIP-7928)
441-
if let Some(recorder) = self.db.bal_recorder.as_mut() {
442-
recorder.record_touched_address(address);
443-
// If EIP-7702 delegation, also record the delegation target (code source)
444-
if is_delegation_7702 {
445-
recorder.record_touched_address(code_address);
446-
}
447-
}
448-
449560
// OPERATION
450561
let value = U256::zero();
451562
let from = callframe.to; // The new sender will be the current contract.
@@ -717,14 +828,6 @@ impl<'a> VM<'a> {
717828
None => calculate_create_address(deployer, deployer_nonce),
718829
};
719830

720-
// Add new contract to accessed addresses
721-
self.substate.add_accessed_address(new_address);
722-
723-
// Record address touch for BAL (after address is calculated per EIP-7928)
724-
if let Some(recorder) = self.db.bal_recorder.as_mut() {
725-
recorder.record_touched_address(new_address);
726-
}
727-
728831
// Log CREATE in tracer
729832
let call_type = match salt {
730833
Some(_) => CallType::CREATE2,
@@ -740,6 +843,7 @@ impl<'a> VM<'a> {
740843
.ok_or(InternalError::Overflow)?;
741844

742845
// Validations that push 0 (FAIL) to the stack and return reserved gas to deployer
846+
// Per reference: these checks happen BEFORE the new address is tracked for BAL.
743847
// 1. Sender doesn't have enough balance to send value.
744848
// 2. Depth limit has been reached
745849
// 3. Sender nonce is max.
@@ -755,6 +859,14 @@ impl<'a> VM<'a> {
755859
}
756860
}
757861

862+
// Add new contract to accessed addresses (after early checks pass, per reference)
863+
self.substate.add_accessed_address(new_address);
864+
865+
// Record address touch for BAL (after early checks pass per EIP-7928 reference)
866+
if let Some(recorder) = self.db.bal_recorder.as_mut() {
867+
recorder.record_touched_address(new_address);
868+
}
869+
758870
// Increment sender nonce (irreversible change)
759871
self.increment_account_nonce(deployer)?;
760872

0 commit comments

Comments
 (0)