Skip to content

Commit 6917b13

Browse files
authored
Add support for EIP-7623 (#389)
* feat: ✨ add support for EIP-7623 * feat: ✨ add post_execution method to gasometer * fix: 🐛 check for invalid gas limit * feat: ✨ add gas tracker * style: 🎨 fmt * fix: 🐛 add check for recorded transaction * Revert "fix: 🐛 add check for recorded transaction" This reverts commit e129161. * fix: 🐛 use saturanting arithmetic in execution gas calculation * refactor: 🔥 remove apply_eip_7623_adjustment function * fix: 🐛 fix computation of EIP-7623 cost * fix: 🐛 fix execution cost computation * refactor: ♻️ move cost computation inside GasTracker * perf: ⚡ add caching to GasTracker * test: ✅ add proper testing * refactor: 🚨 clippy * refactor: ♻️ rename GasTracker to GasMetrics and move it to a new module * refactor: ♻️ rename execution_cost to non_intrinsic_cost * refactor: ♻️ rename tracker to metrics * docs: 📝 improve docs * fix: 🐛 add instrinsic gas cost metric * docs: 📝 document intrinsic_cost_metrics method * refactor: ♻️ add init method to GasMetrics * refactor: ♻️ reduce code duplication * refactor: ♻️ improve if condition * fix: 🐛 add contract creation component to intrinsic gas calculation * fix: 🐛 remove duplicated init_code_cost component * refactor: 🔥 remove intrinsic cost metrics * test: ✅ test invalid transaction with gas lower than floor/intrinsic cost * refactor: ♻️ wildly simplify the solution * revert: ⏪ remove refactoring
1 parent 8975194 commit 6917b13

File tree

4 files changed

+1636
-12
lines changed

4 files changed

+1636
-12
lines changed

gasometer/src/lib.rs

Lines changed: 55 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ impl<'config> Gasometer<'config> {
8181
inner: Ok(Inner {
8282
memory_gas: 0,
8383
used_gas: 0,
84+
floor_gas: 0,
8485
refunded_gas: 0,
8586
config,
8687
}),
@@ -241,7 +242,7 @@ impl<'config> Gasometer<'config> {
241242

242243
/// Record transaction cost.
243244
pub fn record_transaction(&mut self, cost: TransactionCost) -> Result<(), ExitError> {
244-
let gas_cost = match cost {
245+
let (gas_cost, floor_gas) = match cost {
245246
TransactionCost::Call {
246247
zero_data_len,
247248
non_zero_data_len,
@@ -257,6 +258,8 @@ impl<'config> Gasometer<'config> {
257258
+ access_list_storage_len as u64 * self.config.gas_access_list_storage_key
258259
+ authorization_list_len as u64 * self.config.gas_per_empty_account_cost;
259260

261+
let floor = floor_gas(zero_data_len, non_zero_data_len, self.config);
262+
260263
log_gas!(
261264
self,
262265
"Record Call {} [gas_transaction_call: {}, zero_data_len: {}, non_zero_data_len: {}, access_list_address_len: {}, access_list_storage_len: {}, authorization_list_len: {}]",
@@ -269,7 +272,7 @@ impl<'config> Gasometer<'config> {
269272
authorization_list_len
270273
);
271274

272-
cost
275+
(cost, floor)
273276
}
274277
TransactionCost::Create {
275278
zero_data_len,
@@ -289,6 +292,8 @@ impl<'config> Gasometer<'config> {
289292
cost += initcode_cost;
290293
}
291294

295+
let floor = floor_gas(zero_data_len, non_zero_data_len, self.config);
296+
292297
log_gas!(
293298
self,
294299
"Record Create {} [gas_transaction_create: {}, zero_data_len: {}, non_zero_data_len: {}, access_list_address_len: {}, access_list_storage_len: {}, initcode_cost: {}, authorization_list_len: {}]",
@@ -301,7 +306,8 @@ impl<'config> Gasometer<'config> {
301306
initcode_cost,
302307
authorization_list_len
303308
);
304-
cost
309+
310+
(cost, floor)
305311
}
306312
};
307313

@@ -310,12 +316,25 @@ impl<'config> Gasometer<'config> {
310316
snapshot: self.snapshot(),
311317
});
312318

319+
// EIP-7623 validation: Check if gas limit meets floor requirement
320+
if self.config.has_eip_7623 {
321+
// Any transaction with a gas limit below:
322+
// 21000 + TOTAL_COST_FLOOR_PER_TOKEN * tokens_in_calldata
323+
if self.gas() < floor_gas {
324+
self.inner = Err(ExitError::OutOfGas);
325+
return Err(ExitError::OutOfGas);
326+
}
327+
}
328+
329+
// Any transaction with a gas limit below its intrinsic gas cost
330+
// is considered invalid.
313331
if self.gas() < gas_cost {
314332
self.inner = Err(ExitError::OutOfGas);
315333
return Err(ExitError::OutOfGas);
316334
}
317335

318336
self.inner_mut()?.used_gas += gas_cost;
337+
self.inner_mut()?.floor_gas += floor_gas;
319338
Ok(())
320339
}
321340

@@ -328,6 +347,19 @@ impl<'config> Gasometer<'config> {
328347
refunded_gas: inner.refunded_gas,
329348
})
330349
}
350+
351+
/// Apply post-execution adjustments for various EIPs.
352+
/// This method handles gas adjustments that need to be calculated after transaction execution.
353+
pub fn post_execution(&mut self) -> Result<(), ExitError> {
354+
// Apply EIP-7623 adjustments
355+
if self.config.has_eip_7623 {
356+
let inner = self.inner_mut()?;
357+
358+
inner.used_gas = max(inner.used_gas, inner.floor_gas);
359+
}
360+
361+
Ok(())
362+
}
331363
}
332364

333365
/// Calculate the call transaction cost.
@@ -385,6 +417,25 @@ pub fn init_code_cost(data: &[u8]) -> u64 {
385417
2 * ((data.len() as u64 + 31) / 32)
386418
}
387419

420+
pub fn floor_gas(
421+
zero_bytes_in_calldata: usize,
422+
non_zero_bytes_in_calldata: usize,
423+
config: &Config,
424+
) -> u64 {
425+
config
426+
.gas_transaction_call
427+
.saturating_add(
428+
config
429+
.gas_calldata_zero_floor
430+
.saturating_mul(zero_bytes_in_calldata as u64),
431+
)
432+
.saturating_add(
433+
config
434+
.gas_calldata_non_zero_floor
435+
.saturating_mul(non_zero_bytes_in_calldata as u64),
436+
)
437+
}
438+
388439
/// Counts the number of addresses and storage keys in the access list
389440
fn count_access_list(access_list: &[(H160, Vec<H256>)]) -> (usize, usize) {
390441
let access_list_address_len = access_list.len();
@@ -799,6 +850,7 @@ pub fn dynamic_opcode_cost<H: Handler>(
799850
struct Inner<'config> {
800851
memory_gas: u64,
801852
used_gas: u64,
853+
floor_gas: u64,
802854
refunded_gas: i64,
803855
config: &'config Config,
804856
}

runtime/src/lib.rs

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -248,6 +248,10 @@ pub struct Config {
248248
pub gas_auth_base_cost: u64,
249249
/// EIP-7702: Gas cost per empty account in authorization list
250250
pub gas_per_empty_account_cost: u64,
251+
/// EIP-7623: Gas cost floor per zero byte
252+
pub gas_calldata_zero_floor: u64,
253+
/// EIP-7623: Gas cost floor per non-zero byte
254+
pub gas_calldata_non_zero_floor: u64,
251255
/// Whether to throw out of gas error when
252256
/// CALL/CALLCODE/DELEGATECALL requires more than maximum amount
253257
/// of gas.
@@ -300,6 +304,8 @@ pub struct Config {
300304
pub has_eip_6780: bool,
301305
/// Has EIP-7702. See [EIP-7702](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-7702.md)
302306
pub has_eip_7702: bool,
307+
/// Has EIP-7623. See [EIP-7623](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-7623.md)
308+
pub has_eip_7623: bool,
303309
}
304310

305311
impl Config {
@@ -360,6 +366,9 @@ impl Config {
360366
has_eip_7702: false,
361367
gas_auth_base_cost: 0,
362368
gas_per_empty_account_cost: 0,
369+
has_eip_7623: false,
370+
gas_calldata_zero_floor: 0,
371+
gas_calldata_non_zero_floor: 0,
363372
}
364373
}
365374

@@ -420,6 +429,9 @@ impl Config {
420429
has_eip_7702: false,
421430
gas_auth_base_cost: 0,
422431
gas_per_empty_account_cost: 0,
432+
has_eip_7623: false,
433+
gas_calldata_zero_floor: 0,
434+
gas_calldata_non_zero_floor: 0,
423435
}
424436
}
425437

@@ -470,6 +482,9 @@ impl Config {
470482
has_eip_7702,
471483
gas_auth_base_cost,
472484
gas_per_empty_account_cost,
485+
has_eip_7623,
486+
gas_calldata_zero_floor,
487+
gas_calldata_non_zero_floor,
473488
} = inputs;
474489

475490
// See https://eips.ethereum.org/EIPS/eip-2929
@@ -539,6 +554,9 @@ impl Config {
539554
has_eip_7702,
540555
gas_auth_base_cost,
541556
gas_per_empty_account_cost,
557+
has_eip_7623,
558+
gas_calldata_zero_floor,
559+
gas_calldata_non_zero_floor,
542560
}
543561
}
544562
}
@@ -561,6 +579,9 @@ struct DerivedConfigInputs {
561579
has_eip_7702: bool,
562580
gas_auth_base_cost: u64,
563581
gas_per_empty_account_cost: u64,
582+
has_eip_7623: bool,
583+
gas_calldata_zero_floor: u64,
584+
gas_calldata_non_zero_floor: u64,
564585
}
565586

566587
impl DerivedConfigInputs {
@@ -581,6 +602,9 @@ impl DerivedConfigInputs {
581602
has_eip_7702: false,
582603
gas_auth_base_cost: 0,
583604
gas_per_empty_account_cost: 0,
605+
has_eip_7623: false,
606+
gas_calldata_zero_floor: 0,
607+
gas_calldata_non_zero_floor: 0,
584608
}
585609
}
586610

@@ -601,6 +625,9 @@ impl DerivedConfigInputs {
601625
has_eip_7702: false,
602626
gas_auth_base_cost: 0,
603627
gas_per_empty_account_cost: 0,
628+
has_eip_7623: false,
629+
gas_calldata_zero_floor: 0,
630+
gas_calldata_non_zero_floor: 0,
604631
}
605632
}
606633

@@ -621,6 +648,9 @@ impl DerivedConfigInputs {
621648
has_eip_7702: false,
622649
gas_auth_base_cost: 0,
623650
gas_per_empty_account_cost: 0,
651+
has_eip_7623: false,
652+
gas_calldata_zero_floor: 0,
653+
gas_calldata_non_zero_floor: 0,
624654
}
625655
}
626656

@@ -642,6 +672,9 @@ impl DerivedConfigInputs {
642672
has_eip_7702: false,
643673
gas_auth_base_cost: 0,
644674
gas_per_empty_account_cost: 0,
675+
has_eip_7623: false,
676+
gas_calldata_zero_floor: 0,
677+
gas_calldata_non_zero_floor: 0,
645678
}
646679
}
647680

@@ -663,6 +696,9 @@ impl DerivedConfigInputs {
663696
has_eip_7702: false,
664697
gas_auth_base_cost: 0,
665698
gas_per_empty_account_cost: 0,
699+
has_eip_7623: false,
700+
gas_calldata_zero_floor: 0,
701+
gas_calldata_non_zero_floor: 0,
666702
}
667703
}
668704

@@ -687,6 +723,9 @@ impl DerivedConfigInputs {
687723
gas_auth_base_cost: 12500,
688724
// PER_EMPTY_ACCOUNT_COST from EIP-7702
689725
gas_per_empty_account_cost: 25000,
726+
has_eip_7623: true,
727+
gas_calldata_zero_floor: 10,
728+
gas_calldata_non_zero_floor: 40,
690729
}
691730
}
692731
}

src/executor/stack/executor.rs

Lines changed: 30 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -496,22 +496,29 @@ impl<'config, 'precompiles, S: StackState<'config>, P: PrecompileSet>
496496
}
497497
}
498498

499-
match self.create_inner(
499+
let result = match self.create_inner(
500500
caller,
501501
CreateScheme::Legacy { caller },
502502
value,
503503
init_code,
504504
Some(gas_limit),
505505
false,
506506
) {
507-
Capture::Exit((s, _, v)) => emit_exit!(s, v),
507+
Capture::Exit((s, _, v)) => (s, v),
508508
Capture::Trap(rt) => {
509509
let mut cs = Vec::with_capacity(DEFAULT_CALL_STACK_CAPACITY);
510510
cs.push(rt.0);
511511
let (s, _, v) = self.execute_with_call_stack(&mut cs, None);
512-
emit_exit!(s, v)
512+
(s, v)
513513
}
514+
};
515+
516+
// Apply post-execution adjustments
517+
if let Err(e) = self.state.metadata_mut().gasometer.post_execution() {
518+
return emit_exit!(e.into(), Vec::new());
514519
}
520+
521+
emit_exit!(result.0, result.1)
515522
}
516523

517524
/// Execute a `CREATE2` transaction.
@@ -560,7 +567,7 @@ impl<'config, 'precompiles, S: StackState<'config>, P: PrecompileSet>
560567
}
561568
}
562569

563-
match self.create_inner(
570+
let result = match self.create_inner(
564571
caller,
565572
CreateScheme::Create2 {
566573
caller,
@@ -572,14 +579,21 @@ impl<'config, 'precompiles, S: StackState<'config>, P: PrecompileSet>
572579
Some(gas_limit),
573580
false,
574581
) {
575-
Capture::Exit((s, _, v)) => emit_exit!(s, v),
582+
Capture::Exit((s, _, v)) => (s, v),
576583
Capture::Trap(rt) => {
577584
let mut cs = Vec::with_capacity(DEFAULT_CALL_STACK_CAPACITY);
578585
cs.push(rt.0);
579586
let (s, _, v) = self.execute_with_call_stack(&mut cs, None);
580-
emit_exit!(s, v)
587+
(s, v)
581588
}
589+
};
590+
591+
// Apply post-execution adjustments
592+
if let Err(e) = self.state.metadata_mut().gasometer.post_execution() {
593+
return emit_exit!(e.into(), Vec::new());
582594
}
595+
596+
emit_exit!(result.0, result.1)
583597
}
584598

585599
/// Execute a `CREATE` transaction that force the contract address
@@ -714,7 +728,7 @@ impl<'config, 'precompiles, S: StackState<'config>, P: PrecompileSet>
714728
apparent_value: value,
715729
};
716730

717-
match self.call_inner(
731+
let result = match self.call_inner(
718732
address,
719733
Some(Transfer {
720734
source: caller,
@@ -728,14 +742,21 @@ impl<'config, 'precompiles, S: StackState<'config>, P: PrecompileSet>
728742
false,
729743
context,
730744
) {
731-
Capture::Exit((s, v)) => emit_exit!(s, v),
745+
Capture::Exit((s, v)) => (s, v),
732746
Capture::Trap(rt) => {
733747
let mut cs = Vec::with_capacity(DEFAULT_CALL_STACK_CAPACITY);
734748
cs.push(rt.0);
735749
let (s, _, v) = self.execute_with_call_stack(&mut cs, Some(caller));
736-
emit_exit!(s, v)
750+
(s, v)
737751
}
752+
};
753+
754+
// Apply post-execution adjustments
755+
if let Err(e) = self.state.metadata_mut().gasometer.post_execution() {
756+
return emit_exit!(e.into(), Vec::new());
738757
}
758+
759+
emit_exit!(result.0, result.1)
739760
}
740761

741762
/// Get used gas for the current executor, given the price.

0 commit comments

Comments
 (0)