Skip to content

Commit 6882e22

Browse files
authored
feat(Prague): EIP-7623 Increase Calldata Cost (#1744)
* feat: eip-7623 * remove double iteration on data
1 parent a002459 commit 6882e22

File tree

6 files changed

+103
-38
lines changed

6 files changed

+103
-38
lines changed

crates/interpreter/src/gas/calc.rs

Lines changed: 50 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -361,38 +361,44 @@ pub const fn memory_gas(num_words: u64) -> u64 {
361361
.saturating_add(num_words.saturating_mul(num_words) / 512)
362362
}
363363

364+
/// Init and floor gas from transaction
365+
#[derive(Clone, Copy, Debug, Default)]
366+
pub struct InitialAndFloorGas {
367+
/// Initial gas for transaction.
368+
pub initial_gas: u64,
369+
/// If transaction is a Call and Prague is enabled
370+
/// floor_gas is at least amount of gas that is going to be spent.
371+
pub floor_gas: u64,
372+
}
373+
364374
/// Initial gas that is deducted for transaction to be included.
365375
/// Initial gas contains initial stipend gas, gas for access list and input data.
366-
pub fn validate_initial_tx_gas(
376+
///
377+
/// # Returns
378+
///
379+
/// - Intrinsic gas
380+
/// - Number of tokens in calldata
381+
pub fn calculate_initial_tx_gas(
367382
spec_id: SpecId,
368383
input: &[u8],
369384
is_create: bool,
370385
access_list: &[AccessListItem],
371386
authorization_list_num: u64,
372-
) -> u64 {
373-
let mut initial_gas = 0;
374-
let zero_data_len = input.iter().filter(|v| **v == 0).count() as u64;
375-
let non_zero_data_len = input.len() as u64 - zero_data_len;
387+
) -> InitialAndFloorGas {
388+
let mut gas = InitialAndFloorGas::default();
376389

377-
// initdate stipend
378-
initial_gas += zero_data_len * TRANSACTION_ZERO_DATA;
379-
// EIP-2028: Transaction data gas cost reduction
380-
initial_gas += non_zero_data_len
381-
* if spec_id.is_enabled_in(SpecId::ISTANBUL) {
382-
16
383-
} else {
384-
68
385-
};
390+
let tokens_in_calldata = get_tokens_in_calldata(input, spec_id.is_enabled_in(SpecId::ISTANBUL));
391+
gas.initial_gas += tokens_in_calldata * STANDARD_TOKEN_COST;
386392

387393
// get number of access list account and storages.
388394
if spec_id.is_enabled_in(SpecId::BERLIN) {
389395
let accessed_slots: usize = access_list.iter().map(|item| item.storage_keys.len()).sum();
390-
initial_gas += access_list.len() as u64 * ACCESS_LIST_ADDRESS;
391-
initial_gas += accessed_slots as u64 * ACCESS_LIST_STORAGE_KEY;
396+
gas.initial_gas += access_list.len() as u64 * ACCESS_LIST_ADDRESS;
397+
gas.initial_gas += accessed_slots as u64 * ACCESS_LIST_STORAGE_KEY;
392398
}
393399

394400
// base stipend
395-
initial_gas += if is_create {
401+
gas.initial_gas += if is_create {
396402
if spec_id.is_enabled_in(SpecId::HOMESTEAD) {
397403
// EIP-2: Homestead Hard-fork Changes
398404
53000
@@ -406,13 +412,36 @@ pub fn validate_initial_tx_gas(
406412
// EIP-3860: Limit and meter initcode
407413
// Init code stipend for bytecode analysis
408414
if spec_id.is_enabled_in(SpecId::SHANGHAI) && is_create {
409-
initial_gas += initcode_cost(input.len() as u64)
415+
gas.initial_gas += initcode_cost(input.len() as u64)
410416
}
411417

412-
// EIP-7702
418+
// EIP-7702
413419
if spec_id.is_enabled_in(SpecId::PRAGUE) {
414-
initial_gas += authorization_list_num * eip7702::PER_EMPTY_ACCOUNT_COST;
420+
gas.initial_gas += authorization_list_num * eip7702::PER_EMPTY_ACCOUNT_COST;
421+
422+
// Calculate gas floor for EIP-7623
423+
gas.floor_gas = calc_tx_floor_cost(tokens_in_calldata);
415424
}
416425

417-
initial_gas
426+
gas
427+
}
428+
429+
/// Retrieve the total number of tokens in calldata.
430+
#[inline]
431+
pub fn get_tokens_in_calldata(input: &[u8], is_istanbul: bool) -> u64 {
432+
let zero_data_len = input.iter().filter(|v| **v == 0).count() as u64;
433+
let non_zero_data_len = input.len() as u64 - zero_data_len;
434+
let non_zero_data_multiplier = if is_istanbul {
435+
// EIP-2028: Transaction data gas cost reduction
436+
NON_ZERO_BYTE_MULTIPLIER_ISTANBUL
437+
} else {
438+
NON_ZERO_BYTE_MULTIPLIER
439+
};
440+
zero_data_len + non_zero_data_len * non_zero_data_multiplier
441+
}
442+
443+
/// Calculate the transaction cost floor as specified in EIP-7623.
444+
#[inline]
445+
pub fn calc_tx_floor_cost(tokens_in_calldata: u64) -> u64 {
446+
tokens_in_calldata * TOTAL_COST_FLOOR_PER_TOKEN + 21_000
418447
}

crates/interpreter/src/gas/constants.rs

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,9 +33,19 @@ pub const SSTORE_SET: u64 = 20000;
3333
pub const SSTORE_RESET: u64 = 5000;
3434
pub const REFUND_SSTORE_CLEARS: i64 = 15000;
3535

36-
pub const TRANSACTION_ZERO_DATA: u64 = 4;
37-
pub const TRANSACTION_NON_ZERO_DATA_INIT: u64 = 16;
38-
pub const TRANSACTION_NON_ZERO_DATA_FRONTIER: u64 = 68;
36+
/// The standard cost of calldata token.
37+
pub const STANDARD_TOKEN_COST: u64 = 4;
38+
/// The cost of a non-zero byte in calldata.
39+
pub const NON_ZERO_BYTE_DATA_COST: u64 = 68;
40+
/// The multiplier for a non zero byte in calldata.
41+
pub const NON_ZERO_BYTE_MULTIPLIER: u64 = NON_ZERO_BYTE_DATA_COST / STANDARD_TOKEN_COST;
42+
/// The cost of a non-zero byte in calldata adjusted by [EIP-2028](https://eips.ethereum.org/EIPS/eip-2028).
43+
pub const NON_ZERO_BYTE_DATA_COST_ISTANBUL: u64 = 16;
44+
/// The multiplier for a non zero byte in calldata adjusted by [EIP-2028](https://eips.ethereum.org/EIPS/eip-2028).
45+
pub const NON_ZERO_BYTE_MULTIPLIER_ISTANBUL: u64 =
46+
NON_ZERO_BYTE_DATA_COST_ISTANBUL / STANDARD_TOKEN_COST;
47+
// The cost floor per token as defined by [EIP-2028](https://eips.ethereum.org/EIPS/eip-2028).
48+
pub const TOTAL_COST_FLOOR_PER_TOKEN: u64 = 10;
3949

4050
pub const EOF_CREATE_GAS: u64 = 32000;
4151

crates/primitives/src/result.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -262,6 +262,11 @@ pub enum InvalidTransaction {
262262
/// - initial stipend gas
263263
/// - gas for access list and input data
264264
CallGasCostMoreThanGasLimit,
265+
/// Gas floor calculated from EIP-7623 Increase calldata cost
266+
/// is more than the gas limit.
267+
///
268+
/// Tx data is too large to be executed.
269+
GasFloorMoreThanGasLimit,
265270
/// EIP-3607 Reject transactions from senders with deployed code
266271
RejectCallerWithCode,
267272
/// Transaction account does not have enough amount of ether to cover transferred value and gas_limit*gas_price.
@@ -352,6 +357,9 @@ impl fmt::Display for InvalidTransaction {
352357
Self::CallGasCostMoreThanGasLimit => {
353358
write!(f, "call gas cost exceeds the gas limit")
354359
}
360+
Self::GasFloorMoreThanGasLimit => {
361+
write!(f, "gas floor exceeds the gas limit")
362+
}
355363
Self::RejectCallerWithCode => {
356364
write!(f, "reject transactions from senders with deployed code")
357365
}

crates/revm/src/evm.rs

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
use revm_interpreter::gas::InitialAndFloorGas;
2+
13
use crate::{
24
builder::{EvmBuilder, HandlerStage, SetGenericStage},
35
db::{Database, DatabaseCommit, EmptyDB},
@@ -195,20 +197,20 @@ impl<EXT, DB: Database> Evm<'_, EXT, DB> {
195197
/// This function will not validate the transaction.
196198
#[inline]
197199
pub fn transact_preverified(&mut self) -> EVMResult<DB::Error> {
198-
let initial_gas_spend = self
200+
let init_and_floor_gas = self
199201
.handler
200202
.validation()
201203
.initial_tx_gas(&self.context.evm.env)
202204
.inspect_err(|_e| self.clear())?;
203-
let output = self.transact_preverified_inner(initial_gas_spend);
205+
let output = self.transact_preverified_inner(init_and_floor_gas);
204206
let output = self.handler.post_execution().end(&mut self.context, output);
205207
self.clear();
206208
output
207209
}
208210

209211
/// Pre verify transaction inner.
210212
#[inline]
211-
fn preverify_transaction_inner(&mut self) -> Result<u64, EVMError<DB::Error>> {
213+
fn preverify_transaction_inner(&mut self) -> Result<InitialAndFloorGas, EVMError<DB::Error>> {
212214
self.handler.validation().env(&self.context.evm.env)?;
213215
let initial_gas_spend = self
214216
.handler
@@ -225,11 +227,11 @@ impl<EXT, DB: Database> Evm<'_, EXT, DB> {
225227
/// This function will validate the transaction.
226228
#[inline]
227229
pub fn transact(&mut self) -> EVMResult<DB::Error> {
228-
let initial_gas_spend = self
230+
let init_and_floor_gas = self
229231
.preverify_transaction_inner()
230232
.inspect_err(|_e| self.clear())?;
231233

232-
let output = self.transact_preverified_inner(initial_gas_spend);
234+
let output = self.transact_preverified_inner(init_and_floor_gas);
233235
let output = self.handler.post_execution().end(&mut self.context, output);
234236
self.clear();
235237
output
@@ -319,7 +321,7 @@ impl<EXT, DB: Database> Evm<'_, EXT, DB> {
319321
}
320322

321323
/// Transact pre-verified transaction.
322-
fn transact_preverified_inner(&mut self, initial_gas_spend: u64) -> EVMResult<DB::Error> {
324+
fn transact_preverified_inner(&mut self, gas: InitialAndFloorGas) -> EVMResult<DB::Error> {
323325
let spec_id = self.spec_id();
324326
let ctx = &mut self.context;
325327
let pre_exec = self.handler.pre_execution();
@@ -334,7 +336,7 @@ impl<EXT, DB: Database> Evm<'_, EXT, DB> {
334336
// deduce caller balance with its limit.
335337
pre_exec.deduct_caller(ctx)?;
336338

337-
let gas_limit = ctx.evm.env.tx.gas_limit - initial_gas_spend;
339+
let gas_limit = ctx.evm.env.tx.gas_limit - gas.initial_gas;
338340

339341
// apply EIP-7702 auth list.
340342
let eip7702_gas_refund = pre_exec.apply_eip7702_auth_list(ctx)? as i64;
@@ -378,6 +380,13 @@ impl<EXT, DB: Database> Evm<'_, EXT, DB> {
378380
.execution()
379381
.last_frame_return(ctx, &mut result)?;
380382

383+
// EIP-7623: Increase calldata cost
384+
// spend at least a gas_floor amount of gas.
385+
let gas_result = result.gas_mut();
386+
if gas_result.spent() < gas.floor_gas {
387+
let _ = gas_result.record_cost(gas.floor_gas - gas_result.spent());
388+
}
389+
381390
let post_exec = self.handler.post_execution();
382391
// calculate final refund and add EIP-7702 refund to gas.
383392
post_exec.refund(ctx, result.gas_mut(), eip7702_gas_refund);

crates/revm/src/handler/handle_types/validation.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
use revm_interpreter::gas::InitialAndFloorGas;
2+
13
use crate::{
24
handler::mainnet,
35
primitives::{db::Database, EVMError, Env, Spec},
@@ -16,7 +18,7 @@ pub type ValidateTxEnvAgainstState<'a, EXT, DB> =
1618

1719
/// Initial gas calculation handle
1820
pub type ValidateInitialTxGasHandle<'a, DB> =
19-
Arc<dyn Fn(&Env) -> Result<u64, EVMError<<DB as Database>::Error>> + 'a>;
21+
Arc<dyn Fn(&Env) -> Result<InitialAndFloorGas, EVMError<<DB as Database>::Error>> + 'a>;
2022

2123
/// Handles related to validation.
2224
pub struct ValidationHandler<'a, EXT, DB: Database> {
@@ -46,7 +48,7 @@ impl<EXT, DB: Database> ValidationHandler<'_, EXT, DB> {
4648
}
4749

4850
/// Initial gas
49-
pub fn initial_tx_gas(&self, env: &Env) -> Result<u64, EVMError<DB::Error>> {
51+
pub fn initial_tx_gas(&self, env: &Env) -> Result<InitialAndFloorGas, EVMError<DB::Error>> {
5052
(self.initial_tx_gas)(env)
5153
}
5254

crates/revm/src/handler/mainnet/validation.rs

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1-
use revm_interpreter::gas;
1+
use revm_interpreter::gas::{self, InitialAndFloorGas};
22

33
use crate::{
4+
handler::SpecId,
45
primitives::{db::Database, EVMError, Env, InvalidTransaction, Spec},
56
Context,
67
};
@@ -38,7 +39,7 @@ pub fn validate_tx_against_state<SPEC: Spec, EXT, DB: Database>(
3839
/// Validate initial transaction gas.
3940
pub fn validate_initial_tx_gas<SPEC: Spec, DB: Database>(
4041
env: &Env,
41-
) -> Result<u64, EVMError<DB::Error>> {
42+
) -> Result<InitialAndFloorGas, EVMError<DB::Error>> {
4243
let input = &env.tx.data;
4344
let is_create = env.tx.transact_to.is_create();
4445
let access_list = &env.tx.access_list;
@@ -49,7 +50,7 @@ pub fn validate_initial_tx_gas<SPEC: Spec, DB: Database>(
4950
.map(|l| l.len() as u64)
5051
.unwrap_or_default();
5152

52-
let initial_gas_spend = gas::validate_initial_tx_gas(
53+
let gas = gas::calculate_initial_tx_gas(
5354
SPEC::SPEC_ID,
5455
input,
5556
is_create,
@@ -58,8 +59,14 @@ pub fn validate_initial_tx_gas<SPEC: Spec, DB: Database>(
5859
);
5960

6061
// Additional check to see if limit is big enough to cover initial gas.
61-
if initial_gas_spend > env.tx.gas_limit {
62+
if gas.initial_gas > env.tx.gas_limit {
6263
return Err(InvalidTransaction::CallGasCostMoreThanGasLimit.into());
6364
}
64-
Ok(initial_gas_spend)
65+
66+
// EIP-7623
67+
if SPEC::SPEC_ID.is_enabled_in(SpecId::PRAGUE) && gas.floor_gas > env.tx.gas_limit {
68+
return Err(InvalidTransaction::GasFloorMoreThanGasLimit.into());
69+
};
70+
71+
Ok(gas)
6572
}

0 commit comments

Comments
 (0)