@@ -319,15 +319,30 @@ impl<'config> Gasometer<'config> {
319319 Ok ( ( ) )
320320 }
321321
322- /// Record transaction cost.
322+ #[ must_use]
323+ pub fn calculate_intrinsic_gas_and_gas_floor (
324+ data : & [ u8 ] ,
325+ access_list : & [ ( H160 , Vec < H256 > ) ] ,
326+ authorization_list_len : usize ,
327+ config : & Config ,
328+ is_contract_creation : bool ,
329+ ) -> ( u64 , u64 ) {
330+ let cost = if is_contract_creation {
331+ create_transaction_cost ( data, access_list)
332+ } else {
333+ call_transaction_cost ( data, access_list, authorization_list_len)
334+ } ;
335+ Self :: intrinsic_gas_and_gas_floor ( cost, config)
336+ }
337+
338+ /// Calculate intrinsic gas and gas floor based on `TransactionCost` type and config.
339+ /// Returns intrinsic gas cost and gas floor.
323340 /// Related EIPs:
324341 /// - [EIP-2028](https://eips.ethereum.org/EIPS/eip-2028)
325342 /// - [EIP-7623](https://eips.ethereum.org/EIPS/eip-7623)
326- ///
327- /// # Errors
328- /// Return `ExitError`
329- pub fn record_transaction ( & mut self , cost : TransactionCost ) -> Result < ( ) , ExitError > {
330- let gas_cost = match cost {
343+ #[ must_use]
344+ pub const fn intrinsic_gas_and_gas_floor ( cost : TransactionCost , config : & Config ) -> ( u64 , u64 ) {
345+ match cost {
331346 // NOTE: in that context usize->u64 `as_conversions` is safe
332347 #[ allow( clippy:: as_conversions) ]
333348 TransactionCost :: Call {
@@ -338,22 +353,78 @@ impl<'config> Gasometer<'config> {
338353 authorization_list_len,
339354 } => {
340355 #[ deny( clippy:: let_and_return) ]
341- let cost = self . config . gas_transaction_call
342- + zero_data_len as u64 * self . config . gas_transaction_zero_data
343- + non_zero_data_len as u64 * self . config . gas_transaction_non_zero_data
344- + access_list_address_len as u64 * self . config . gas_access_list_address
345- + access_list_storage_len as u64 * self . config . gas_access_list_storage_key
346- + authorization_list_len as u64 * self . config . gas_per_empty_account_cost ;
347-
348- if self . config . has_floor_gas {
356+ let cost = config. gas_transaction_call
357+ + zero_data_len as u64 * config. gas_transaction_zero_data
358+ + non_zero_data_len as u64 * config. gas_transaction_non_zero_data
359+ + access_list_address_len as u64 * config. gas_access_list_address
360+ + access_list_storage_len as u64 * config. gas_access_list_storage_key
361+ + authorization_list_len as u64 * config. gas_per_empty_account_cost ;
362+
363+ let floor_gas = if config. has_floor_gas {
349364 // According to EIP-2028: non-zero byte = 16, zero-byte = 4
350365 // According to EIP-7623: tokens_in_calldata = zero_bytes_in_calldata + nonzero_bytes_in_calldata * 4
351366 let tokens_in_calldata = ( zero_data_len + non_zero_data_len * 4 ) as u64 ;
352- self . inner_mut ( ) ?. floor_gas = tokens_in_calldata
353- * self . config . total_cost_floor_per_token
354- + self . config . gas_transaction_call ;
367+ tokens_in_calldata * config. total_cost_floor_per_token
368+ + config. gas_transaction_call
369+ } else {
370+ 0
371+ } ;
372+
373+ ( cost, floor_gas)
374+ }
375+ // NOTE: in that context usize->u64 `as_conversions` is safe
376+ #[ allow( clippy:: as_conversions) ]
377+ TransactionCost :: Create {
378+ zero_data_len,
379+ non_zero_data_len,
380+ access_list_address_len,
381+ access_list_storage_len,
382+ initcode_cost,
383+ } => {
384+ let mut cost = config. gas_transaction_create
385+ + zero_data_len as u64 * config. gas_transaction_zero_data
386+ + non_zero_data_len as u64 * config. gas_transaction_non_zero_data
387+ + access_list_address_len as u64 * config. gas_access_list_address
388+ + access_list_storage_len as u64 * config. gas_access_list_storage_key ;
389+ if config. max_initcode_size . is_some ( ) {
390+ cost += initcode_cost;
355391 }
356392
393+ let floor_gas = if config. has_floor_gas {
394+ // According to EIP-2028: non-zero byte = 16, zero-byte = 4
395+ // According to EIP-7623: tokens_in_calldata = zero_bytes_in_calldata + nonzero_bytes_in_calldata * 4
396+ let tokens_in_calldata = ( zero_data_len + non_zero_data_len * 4 ) as u64 ;
397+ tokens_in_calldata * config. total_cost_floor_per_token
398+ + config. gas_transaction_call
399+ } else {
400+ 0
401+ } ;
402+
403+ ( cost, floor_gas)
404+ }
405+ }
406+ }
407+
408+ /// Verify transaction cost against gas limit and gas floor based on `TransactionCost` type.
409+ /// Returns intrinsic gas cost.
410+ /// For `force-debug` feature, it will log the transaction cost details.
411+ /// Related EIPs:
412+ /// - [EIP-2028](https://eips.ethereum.org/EIPS/eip-2028)
413+ /// - [EIP-7623](https://eips.ethereum.org/EIPS/eip-7623)
414+ ///
415+ /// ## Errors
416+ /// Return `ExitError` if gas limit is less than intrinsic gas or gas floor.
417+ pub fn verify_transaction ( & mut self , cost : TransactionCost ) -> Result < ( u64 , u64 ) , ExitError > {
418+ let ( gas_cost, floor_gas) = Self :: intrinsic_gas_and_gas_floor ( cost, self . config ) ;
419+ #[ cfg( feature = "force-debug" ) ]
420+ match cost {
421+ TransactionCost :: Call {
422+ zero_data_len,
423+ non_zero_data_len,
424+ access_list_address_len,
425+ access_list_storage_len,
426+ authorization_list_len,
427+ } => {
357428 log_gas ! (
358429 self ,
359430 "Record Call {} [gas_transaction_call: {}, zero_data_len: {}, non_zero_data_len: {}, access_list_address_len: {}, access_list_storage_len: {}, authorization_list_len: {}]" ,
@@ -365,36 +436,14 @@ impl<'config> Gasometer<'config> {
365436 access_list_storage_len,
366437 authorization_list_len
367438 ) ;
368-
369- cost
370439 }
371- // NOTE: in that context usize->u64 `as_conversions` is safe
372- #[ allow( clippy:: as_conversions) ]
373440 TransactionCost :: Create {
374441 zero_data_len,
375442 non_zero_data_len,
376443 access_list_address_len,
377444 access_list_storage_len,
378445 initcode_cost,
379446 } => {
380- let mut cost = self . config . gas_transaction_create
381- + zero_data_len as u64 * self . config . gas_transaction_zero_data
382- + non_zero_data_len as u64 * self . config . gas_transaction_non_zero_data
383- + access_list_address_len as u64 * self . config . gas_access_list_address
384- + access_list_storage_len as u64 * self . config . gas_access_list_storage_key ;
385- if self . config . max_initcode_size . is_some ( ) {
386- cost += initcode_cost;
387- }
388-
389- if self . config . has_floor_gas {
390- // According to EIP-2028: non-zero byte = 16, zero-byte = 4
391- // According to EIP-7623: tokens_in_calldata = zero_bytes_in_calldata + nonzero_bytes_in_calldata * 4
392- let tokens_in_calldata = ( zero_data_len + non_zero_data_len * 4 ) as u64 ;
393- self . inner_mut ( ) ?. floor_gas = tokens_in_calldata
394- * self . config . total_cost_floor_per_token
395- + self . config . gas_transaction_call ;
396- }
397-
398447 log_gas ! (
399448 self ,
400449 "Record Create {} [gas_transaction_create: {}, zero_data_len: {}, non_zero_data_len: {}, access_list_address_len: {}, access_list_storage_len: {}, initcode_cost: {}]" ,
@@ -406,18 +455,41 @@ impl<'config> Gasometer<'config> {
406455 access_list_storage_len,
407456 initcode_cost
408457 ) ;
409- cost
410458 }
411- } ;
459+ }
460+ if self . gas ( ) < gas_cost {
461+ self . inner = Err ( ExitError :: OutOfGas ) ;
462+ return Err ( ExitError :: OutOfGas ) ;
463+ }
464+ // EIP-7623 gas floor check for gas_limit
465+ // It's equivalent to checking: max(cas_cost, floor_gas). But as we need to check
466+ // `config.has_floor_gas` anyway, we can do it this way to avoid an extra max() call.
467+ if self . config . has_floor_gas && self . gas_limit ( ) < floor_gas {
468+ self . inner = Err ( ExitError :: OutOfGas ) ;
469+ return Err ( ExitError :: OutOfGas ) ;
470+ }
471+
472+ Ok ( ( gas_cost, floor_gas) )
473+ }
474+
475+ /// Record transaction cost.
476+ /// Related EIPs:
477+ /// - [EIP-2028](https://eips.ethereum.org/EIPS/eip-2028)
478+ /// - [EIP-7623](https://eips.ethereum.org/EIPS/eip-7623)
479+ ///
480+ /// # Errors
481+ /// Return `ExitError`
482+ pub fn record_transaction ( & mut self , cost : TransactionCost ) -> Result < ( ) , ExitError > {
483+ let ( gas_cost, floor_gas) = self . verify_transaction ( cost) ?;
412484
413485 event ! ( RecordTransaction {
414486 cost: gas_cost,
415487 snapshot: self . snapshot( ) ,
416488 } ) ;
417489
418- if self . gas ( ) < gas_cost {
419- self . inner = Err ( ExitError :: OutOfGas ) ;
420- return Err ( ExitError :: OutOfGas ) ;
490+ // EIP-7623 gas floor update
491+ if self . config . has_floor_gas {
492+ self . inner_mut ( ) ? . floor_gas = floor_gas ;
421493 }
422494
423495 self . inner_mut ( ) ?. used_gas += gas_cost;
0 commit comments