@@ -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