@@ -319,17 +319,53 @@ impl<'config> Gasometer<'config> {
319319 Ok ( ( ) )
320320 }
321321
322- /// Record transaction cost.
322+ /// Calculate intrinsic gas and gas floor based on transaction data.
323+ /// Returns intrinsic gas cost and gas floor.
324+ #[ must_use]
325+ pub fn calculate_intrinsic_gas_and_gas_floor (
326+ data : & [ u8 ] ,
327+ access_list : & [ ( H160 , Vec < H256 > ) ] ,
328+ authorization_list_len : usize ,
329+ config : & Config ,
330+ is_contract_creation : bool ,
331+ ) -> ( u64 , u64 ) {
332+ let cost = if is_contract_creation {
333+ create_transaction_cost ( data, access_list)
334+ } else {
335+ call_transaction_cost ( data, access_list, authorization_list_len)
336+ } ;
337+ Self :: intrinsic_gas_and_gas_floor ( cost, config)
338+ }
339+
340+ /// Calculate intrinsic gas and gas floor based on `TransactionCost` type and config.
341+ /// Returns intrinsic gas cost and gas floor.
323342 /// Related EIPs:
324343 /// - [EIP-2028](https://eips.ethereum.org/EIPS/eip-2028)
325344 /// - [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 {
331- // NOTE: in that context usize->u64 `as_conversions` is safe
332- #[ allow( clippy:: as_conversions) ]
345+ #[ must_use]
346+ #[ allow( clippy:: as_conversions) ] // NOTE: in that context usize->u64 `as_conversions` is safe
347+ pub const fn intrinsic_gas_and_gas_floor ( cost : TransactionCost , config : & Config ) -> ( u64 , u64 ) {
348+ const fn floor_gas_calc (
349+ config : & Config ,
350+ zero_data_len : usize ,
351+ non_zero_data_len : usize ,
352+ ) -> u64 {
353+ if config. has_floor_gas {
354+ // According to EIP-2028: non-zero byte = 16, zero-byte = 4
355+ // According to EIP-7623: tokens_in_calldata = zero_bytes_in_calldata + nonzero_bytes_in_calldata * 4
356+ let tokens_in_calldata = non_zero_data_len
357+ . saturating_mul ( 4 )
358+ . saturating_add ( zero_data_len) as u64 ;
359+
360+ tokens_in_calldata
361+ . saturating_mul ( config. total_cost_floor_per_token )
362+ . saturating_add ( config. gas_transaction_call )
363+ } else {
364+ 0
365+ }
366+ }
367+
368+ match cost {
333369 TransactionCost :: Call {
334370 zero_data_len,
335371 non_zero_data_len,
@@ -338,22 +374,98 @@ impl<'config> Gasometer<'config> {
338374 authorization_list_len,
339375 } => {
340376 #[ 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 {
349- // According to EIP-2028: non-zero byte = 16, zero-byte = 4
350- // According to EIP-7623: tokens_in_calldata = zero_bytes_in_calldata + nonzero_bytes_in_calldata * 4
351- 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 ;
377+ let cost = config
378+ . gas_transaction_call
379+ . saturating_add (
380+ config
381+ . gas_transaction_zero_data
382+ . saturating_mul ( zero_data_len as u64 ) ,
383+ )
384+ . saturating_add (
385+ config
386+ . gas_transaction_non_zero_data
387+ . saturating_mul ( non_zero_data_len as u64 ) ,
388+ )
389+ . saturating_add (
390+ config
391+ . gas_access_list_address
392+ . saturating_mul ( access_list_address_len as u64 ) ,
393+ )
394+ . saturating_add (
395+ config
396+ . gas_access_list_storage_key
397+ . saturating_mul ( access_list_storage_len as u64 ) ,
398+ )
399+ . saturating_add (
400+ config
401+ . gas_per_empty_account_cost
402+ . saturating_mul ( authorization_list_len as u64 ) ,
403+ ) ;
404+ let floor_gas = floor_gas_calc ( config, zero_data_len, non_zero_data_len) ;
405+
406+ ( cost, floor_gas)
407+ }
408+ TransactionCost :: Create {
409+ zero_data_len,
410+ non_zero_data_len,
411+ access_list_address_len,
412+ access_list_storage_len,
413+ initcode_cost,
414+ } => {
415+ let mut cost = config
416+ . gas_transaction_create
417+ . saturating_add (
418+ config
419+ . gas_transaction_zero_data
420+ . saturating_mul ( zero_data_len as u64 ) ,
421+ )
422+ . saturating_add (
423+ config
424+ . gas_transaction_non_zero_data
425+ . saturating_mul ( non_zero_data_len as u64 ) ,
426+ )
427+ . saturating_add (
428+ config
429+ . gas_access_list_address
430+ . saturating_mul ( access_list_address_len as u64 ) ,
431+ )
432+ . saturating_add (
433+ config
434+ . gas_access_list_storage_key
435+ . saturating_mul ( access_list_storage_len as u64 ) ,
436+ ) ;
437+
438+ if config. max_initcode_size . is_some ( ) {
439+ cost = cost. saturating_add ( initcode_cost) ;
355440 }
356441
442+ let floor_gas = floor_gas_calc ( config, zero_data_len, non_zero_data_len) ;
443+
444+ ( cost, floor_gas)
445+ }
446+ }
447+ }
448+
449+ /// Verify transaction cost against gas limit and gas floor based on `TransactionCost` type.
450+ /// Returns intrinsic gas cost.
451+ /// For `force-debug` feature, it will log the transaction cost details.
452+ /// Related EIPs:
453+ /// - [EIP-2028](https://eips.ethereum.org/EIPS/eip-2028)
454+ /// - [EIP-7623](https://eips.ethereum.org/EIPS/eip-7623)
455+ ///
456+ /// ## Errors
457+ /// Return `ExitError` if gas limit is less than intrinsic gas or gas floor.
458+ pub fn verify_transaction ( & mut self , cost : TransactionCost ) -> Result < ( u64 , u64 ) , ExitError > {
459+ let ( gas_cost, floor_gas) = Self :: intrinsic_gas_and_gas_floor ( cost, self . config ) ;
460+ #[ cfg( feature = "force-debug" ) ]
461+ match cost {
462+ TransactionCost :: Call {
463+ zero_data_len,
464+ non_zero_data_len,
465+ access_list_address_len,
466+ access_list_storage_len,
467+ authorization_list_len,
468+ } => {
357469 log_gas ! (
358470 self ,
359471 "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 +477,14 @@ impl<'config> Gasometer<'config> {
365477 access_list_storage_len,
366478 authorization_list_len
367479 ) ;
368-
369- cost
370480 }
371- // NOTE: in that context usize->u64 `as_conversions` is safe
372- #[ allow( clippy:: as_conversions) ]
373481 TransactionCost :: Create {
374482 zero_data_len,
375483 non_zero_data_len,
376484 access_list_address_len,
377485 access_list_storage_len,
378486 initcode_cost,
379487 } => {
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-
398488 log_gas ! (
399489 self ,
400490 "Record Create {} [gas_transaction_create: {}, zero_data_len: {}, non_zero_data_len: {}, access_list_address_len: {}, access_list_storage_len: {}, initcode_cost: {}]" ,
@@ -406,18 +496,41 @@ impl<'config> Gasometer<'config> {
406496 access_list_storage_len,
407497 initcode_cost
408498 ) ;
409- cost
410499 }
411- } ;
500+ }
501+ if self . gas ( ) < gas_cost {
502+ self . inner = Err ( ExitError :: OutOfGas ) ;
503+ return Err ( ExitError :: OutOfGas ) ;
504+ }
505+ // EIP-7623 gas floor check for gas_limit
506+ // It's equivalent to checking: max(gas_cost, floor_gas). But as we need to check
507+ // `config.has_floor_gas` anyway, we can do it this way to avoid an extra max() call.
508+ if self . config . has_floor_gas && self . gas_limit ( ) < floor_gas {
509+ self . inner = Err ( ExitError :: OutOfGas ) ;
510+ return Err ( ExitError :: OutOfGas ) ;
511+ }
512+
513+ Ok ( ( gas_cost, floor_gas) )
514+ }
515+
516+ /// Record transaction cost.
517+ /// Related EIPs:
518+ /// - [EIP-2028](https://eips.ethereum.org/EIPS/eip-2028)
519+ /// - [EIP-7623](https://eips.ethereum.org/EIPS/eip-7623)
520+ ///
521+ /// # Errors
522+ /// Return `ExitError`
523+ pub fn record_transaction ( & mut self , cost : TransactionCost ) -> Result < ( ) , ExitError > {
524+ let ( gas_cost, floor_gas) = self . verify_transaction ( cost) ?;
412525
413526 event ! ( RecordTransaction {
414527 cost: gas_cost,
415528 snapshot: self . snapshot( ) ,
416529 } ) ;
417530
418- if self . gas ( ) < gas_cost {
419- self . inner = Err ( ExitError :: OutOfGas ) ;
420- return Err ( ExitError :: OutOfGas ) ;
531+ // EIP-7623 gas floor update
532+ if self . config . has_floor_gas {
533+ self . inner_mut ( ) ? . floor_gas = floor_gas ;
421534 }
422535
423536 self . inner_mut ( ) ?. used_gas += gas_cost;
0 commit comments