Skip to content

Commit cd8d8e1

Browse files
committed
Gasometr: refactoring and improvements
1 parent 6296005 commit cd8d8e1

1 file changed

Lines changed: 116 additions & 44 deletions

File tree

evm/src/gasometer/mod.rs

Lines changed: 116 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)