Skip to content

Commit 7e5d817

Browse files
mrLSDaleksuss
andauthored
Feat: merge rlease v2.2.1 - Prague refactoring (#104)
* Feat: gasometr refactoring and improvements (#100) * Gasometr: refactoring and improvements * Bump from version: v2.2.1 * Extend doc comment for calculate_intrinsic_gas_and_gas_floor * feat: introduce gas_floor_base_cost for EIP-7623 compliance (#101) * feat: introduce gas_floor_base_cost for EIP-7623 compliance * fix: replace simple arithmetic operations with saturating_* analogs for gas calculations * chore: remove gas_floor_base_cost constant --------- Co-authored-by: Oleksandr Anyshchenko <oleksandr.anyshchenko@aurora.dev> * Fix typo --------- Co-authored-by: Oleksandr Anyshchenko <oleksandr.anyshchenko@aurora.dev>
1 parent f5ed625 commit 7e5d817

2 files changed

Lines changed: 163 additions & 50 deletions

File tree

evm/src/gasometer/mod.rs

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

evm/src/runtime/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -173,7 +173,7 @@ pub struct Config {
173173
/// Gas paid for a contract creation transaction.
174174
pub gas_transaction_create: u64,
175175
/// Gas paid for a message call transaction.
176-
pub gas_transaction_call: u64,
176+
pub gas_transaction_call: u64, // TODO: Rename in 3.0.0 because the constant is related not to transaction call only.
177177
/// Gas paid for zero data in a transaction.
178178
pub gas_transaction_zero_data: u64,
179179
/// Gas paid for non-zero data in a transaction.

0 commit comments

Comments
 (0)