-
Notifications
You must be signed in to change notification settings - Fork 953
feat: Gas params #3132
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
feat: Gas params #3132
Conversation
CodSpeed Performance ReportMerging #3132 will not alter performanceComparing Summary
|
| table[TLOAD as usize] = Instruction::new(host::tload, 100); | ||
| table[TSTORE as usize] = Instruction::new(host::tstore, 100); | ||
| table[MCOPY as usize] = Instruction::new(memory::mcopy, 0); // static 2, mostly dynamic | ||
| table[MCOPY as usize] = Instruction::new(memory::mcopy, 3); // static 2, mostly dynamic |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
MCOPY has a static VERY_LOG gas
| gas_or_fail!(context.interpreter, gas::copy_cost_verylow(len)); |
VERYLOW is 3gas.
revm/crates/interpreter/src/gas/calc.rs
Lines 110 to 111 in d0bb48e
| pub const fn copy_cost_verylow(len: usize) -> Option<u64> { | |
| copy_cost(VERYLOW, len) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
revm/crates/interpreter/src/gas/constants.rs
Lines 7 to 8 in fdf606d
| pub const VERYLOW: u64 = 3; | |
| /// Gas cost for DATALOADN instruction. |
| } else { | ||
| 20 | ||
| }; | ||
| gas!(context.interpreter, gas); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Moved to static cost in instructions.rs. Left comments there.
Having this at the start does not matter for this opcode
| } else { | ||
| 400 | ||
| }; | ||
| gas!(context.interpreter, gas); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Moved to static cost in instructions.rs. Left comments there.
Moving this to the start does not matter for this opcode
| } else { | ||
| 20 | ||
| }; | ||
| gas!(context.interpreter, gas); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Moved to static cost in instructions.rs. Left comments there.
Moving this to the start does not matter for this opcode
| } else { | ||
| 20 | ||
| }; | ||
| gas!(context.interpreter, gas); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Moved to static cost in instructions.rs. Left comments there.
Moving this to the start does not matter for this opcode
| @@ -1,70 +1,10 @@ | |||
| use super::constants::*; | |||
| use crate::{num_words, tri, SStoreResult, SelfDestructResult, StateLoad}; | |||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Most of this logic is moved to GasParams. Will add comment there
Initial gas logic is still intact, and will need a follow-up to integrate it with GasParams
| table[GasId::new_account_cost_for_selfdestruct().as_usize()] = 0; | ||
|
|
||
| if spec.is_enabled_in(SpecId::TANGERINE) { | ||
| table[GasId::new_account_cost_for_selfdestruct().as_usize()] = gas::NEWACCOUNT; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is used in GasParams::selfdestruct_cost function
revm/crates/interpreter/src/gas/params.rs
Lines 210 to 217 in 403f28f
| pub fn selfdestruct_cost(&self, should_charge_topup: bool, is_cold: bool) -> u64 { | |
| let mut gas = 0; | |
| // EIP-150: Gas cost changes for IO-heavy operations | |
| if should_charge_topup { | |
| gas += self.new_account_cost_for_selfdestruct(); | |
| } | |
| // EIP-150: Gas cost changes for IO-heavy operations | ||
| if is_tangerine && should_charge_topup { | ||
| gas += NEWACCOUNT | ||
| } | ||
|
|
||
| if res.is_cold { | ||
| gas += selfdestruct_cold_beneficiary_cost(spec_id); | ||
| } | ||
|
|
||
| gas |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This part can now be found in:
revm/crates/interpreter/src/gas/params.rs
Lines 210 to 227 in 403f28f
| pub fn selfdestruct_cost(&self, should_charge_topup: bool, is_cold: bool) -> u64 { | |
| let mut gas = 0; | |
| // EIP-150: Gas cost changes for IO-heavy operations | |
| if should_charge_topup { | |
| gas += self.new_account_cost_for_selfdestruct(); | |
| } | |
| if is_cold { | |
| // Note: SELFDESTRUCT does not charge a WARM_STORAGE_READ_COST in case the recipient is already warm, | |
| // which differs from how the other call-variants work. The reasoning behind this is to keep | |
| // the changes small, a SELFDESTRUCT already costs 5K and is a no-op if invoked more than once. | |
| // | |
| // For GasParams both values are zero before BERLIN fork. | |
| gas += self.cold_account_additional_cost() + self.warm_storage_read_cost(); | |
| } | |
| gas | |
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Selfdestruct logic is slightly different from other EIP-2929 changes but in general, it is correct
| let should_charge_topup = if spec_id.is_enabled_in(SpecId::SPURIOUS_DRAGON) { | ||
| res.data.had_value && !res.data.target_exists | ||
| } else { | ||
| !res.data.target_exists | ||
| }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This logic is moved to selfdestruct instruction:
revm/crates/interpreter/src/instructions/host.rs
Lines 379 to 385 in 403f28f
| // EIP-161: State trie clearing (invariant-preserving alternative) | |
| let should_charge_topup = if spec.is_enabled_in(SpecId::SPURIOUS_DRAGON) { | |
| res.had_value && !res.target_exists | |
| } else { | |
| !res.target_exists | |
| }; | |
| // Account access. | ||
| let mut gas = if spec_id.is_enabled_in(SpecId::BERLIN) { | ||
| WARM_STORAGE_READ_COST | ||
| } else if spec_id.is_enabled_in(SpecId::TANGERINE) { | ||
| // EIP-150: Gas cost changes for IO-heavy operations | ||
| 700 | ||
| } else { | ||
| 40 | ||
| }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
moved to static gas
| // Transfer value cost | ||
| if has_transfer { | ||
| gas += CALLVALUE; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| 5000 | ||
| } else { | ||
| 0 | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Part of static gas
| const fn frontier_sstore_cost(vals: &SStoreResult) -> u64 { | ||
| if vals.is_present_zero() && !vals.is_new_zero() { | ||
| SSTORE_SET | ||
| } else { | ||
| SSTORE_RESET | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Frontier part as it has special logic is moved here:
revm/crates/interpreter/src/gas/params.rs
Lines 271 to 281 in 403f28f
| pub fn sstore_dynamic_gas(&self, is_istanbul: bool, vals: &SStoreResult, is_cold: bool) -> u64 { | |
| // frontier logic gets charged for every SSTORE operation if original value is zero. | |
| // this behaviour is fixed in istanbul fork. | |
| if !is_istanbul { | |
| if vals.is_present_zero() && !vals.is_new_zero() { | |
| return self.sstore_set_without_load_cost(); | |
| } else { | |
| return self.sstore_reset_without_cold_load_cost(); | |
| } | |
| } | |
This is without load cost that is now part of static instruction gas
| let mut gas_cost = istanbul_sstore_cost::<WARM_STORAGE_READ_COST, WARM_SSTORE_RESET>(vals); | ||
|
|
||
| if is_cold { | ||
| gas_cost += COLD_SLOAD_COST; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is cold is now always added but cold cost is zero before berlin here:
revm/crates/interpreter/src/gas/params.rs
Lines 284 to 288 in 403f28f
| // this will be zero before berlin fork. | |
| if is_cold { | |
| gas += self.cold_storage_cost(); | |
| } | |
| if vals.is_new_eq_present() { | ||
| SLOAD_GAS | ||
| } else if vals.is_original_eq_present() && vals.is_original_zero() { | ||
| SSTORE_SET | ||
| } else if vals.is_original_eq_present() { | ||
| SSTORE_RESET_GAS | ||
| } else { | ||
| SLOAD_GAS | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This code is slighly changes as SLOAD_GAS is not part of the static instruction gas. So we have fewer branches
It is easy to follow commnets
revm/crates/interpreter/src/gas/params.rs
Lines 289 to 300 in 403f28f
| // if new values changed present value and present value is unchanged from original. | |
| if vals.new_values_changes_present() && vals.is_original_eq_present() { | |
| gas += if vals.is_original_zero() { | |
| // set cost for creating storage slot (Zero slot means it is not existing). | |
| // and previous condition says present is same as original. | |
| self.sstore_set_without_load_cost() | |
| } else { | |
| // if new value is not zero, this means we are setting some value to it. | |
| self.sstore_reset_without_cold_load_cost() | |
| }; | |
| } | |
| gas |
| pub const fn static_sstore_cost(spec_id: SpecId) -> u64 { | ||
| if spec_id.is_enabled_in(SpecId::BERLIN) { | ||
| WARM_STORAGE_READ_COST | ||
| } else if spec_id.is_enabled_in(SpecId::ISTANBUL) { | ||
| ISTANBUL_SLOAD_GAS | ||
| } else { | ||
| SSTORE_RESET | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is moved to a variable inside GasParams.
revm/crates/interpreter/src/gas/params.rs
Line 157 in 403f28f
| table[GasId::sstore_static().as_usize()] = gas::WARM_STORAGE_READ_COST; |
revm/crates/interpreter/src/gas/params.rs
Line 149 in 403f28f
| table[GasId::sstore_static().as_usize()] = gas::ISTANBUL_SLOAD_GAS; |
revm/crates/interpreter/src/gas/params.rs
Line 127 in 403f28f
| table[GasId::sstore_static().as_usize()] = 5000; |
| pub const fn sload_cost(spec_id: SpecId, is_cold: bool) -> u64 { | ||
| if spec_id.is_enabled_in(SpecId::BERLIN) { | ||
| if is_cold { | ||
| COLD_SLOAD_COST | ||
| } else { | ||
| WARM_STORAGE_READ_COST | ||
| } | ||
| } else if spec_id.is_enabled_in(SpecId::ISTANBUL) { | ||
| // EIP-1884: Repricing for trie-size-dependent opcodes | ||
| ISTANBUL_SLOAD_GAS | ||
| } else if spec_id.is_enabled_in(SpecId::TANGERINE) { | ||
| // EIP-150: Gas cost changes for IO-heavy operations | ||
| 200 | ||
| } else { | ||
| 50 | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Has become part of static instruction gas.
And additional gas is handled here:
revm/crates/interpreter/src/instructions/host.rs
Lines 193 to 210 in 403f28f
| pub fn sload<WIRE: InterpreterTypes, H: Host + ?Sized>(context: InstructionContext<'_, H, WIRE>) { | |
| popn_top!([], index, context.interpreter); | |
| let spec_id = context.interpreter.runtime_flag.spec_id(); | |
| let target = context.interpreter.input.target_address(); | |
| if spec_id.is_enabled_in(BERLIN) { | |
| let additional_cold_cost = context | |
| .interpreter | |
| .gas_params | |
| .cold_storage_additional_cost(); | |
| let skip_cold = context.interpreter.gas.remaining() < additional_cold_cost; | |
| let res = context.host.sload_skip_cold_load(target, *index, skip_cold); | |
| match res { | |
| Ok(storage) => { | |
| if storage.is_cold { | |
| gas!(context.interpreter, additional_cold_cost); | |
| } | |
| if power.is_zero() { | ||
| Some(EXP) | ||
| } else { | ||
| // EIP-160: EXP cost increase | ||
| let gas_byte = U256::from(if spec_id.is_enabled_in(SpecId::SPURIOUS_DRAGON) { | ||
| 50 | ||
| } else { | ||
| 10 | ||
| }); | ||
| let gas = U256::from(EXP) | ||
| .checked_add(gas_byte.checked_mul(U256::from(log2floor(power) / 8 + 1))?)?; | ||
|
|
||
| u64::try_from(gas).ok() | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Dynamic part is in GasParams here:
revm/crates/interpreter/src/gas/params.rs
Lines 193 to 201 in 403f28f
| pub fn exp_cost(&self, power: U256) -> u64 { | |
| if power.is_zero() { | |
| return 0; | |
| } | |
| // EIP-160: EXP cost increase | |
| self.get(GasId::exp_byte_gas()) | |
| .saturating_mul(log2floor(power) / 8 + 1) | |
| } | |
revm/crates/interpreter/src/gas/params.rs
Lines 144 to 147 in 403f28f
| if spec.is_enabled_in(SpecId::SPURIOUS_DRAGON) { | |
| table[GasId::exp_byte_gas().as_usize()] = 50; | |
| } | |
revm/crates/interpreter/src/gas/params.rs
Lines 110 to 111 in 403f28f
| table[GasId::exp_byte_gas().as_usize()] = 10; | |
| table[GasId::logdata().as_usize()] = gas::LOGDATA; |
And static part (EXP) is now part of static instruction gas
| pub const fn create2_cost(len: usize) -> Option<u64> { | ||
| CREATE.checked_add(tri!(cost_per_word(len, KECCAK256WORD))) | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fully moved to:
revm/crates/interpreter/src/gas/params.rs
Lines 399 to 405 in 403f28f
| pub fn create2_cost(&self, len: usize) -> u64 { | |
| self.get(GasId::create()).saturating_add( | |
| self.get(GasId::keccak256_per_word()) | |
| .saturating_mul(num_words(len) as u64), | |
| ) | |
| } | |
revm/crates/interpreter/src/gas/params.rs
Line 120 in 403f28f
| table[GasId::create().as_usize()] = gas::CREATE; |
revm/crates/interpreter/src/gas/params.rs
Line 116 in 403f28f
| table[GasId::keccak256_per_word().as_usize()] = gas::KECCAK256WORD; |
| if !vals.is_present_zero() && vals.is_new_zero() { | ||
| REFUND_SSTORE_CLEARS | ||
| } else { | ||
| 0 | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
before instanbul is moved here:
revm/crates/interpreter/src/gas/params.rs
Lines 305 to 316 in 403f28f
| pub fn sstore_refund(&self, is_istanbul: bool, vals: &SStoreResult) -> i64 { | |
| // EIP-3529: Reduction in refunds | |
| let sstore_clearing_slot_refund = self.sstore_clearing_slot_refund() as i64; | |
| if !is_istanbul { | |
| // // before istanbul fork, refund was always awarded without checking original state. | |
| if !vals.is_present_zero() && vals.is_new_zero() { | |
| return sstore_clearing_slot_refund; | |
| } | |
| return 0; | |
| } | |
| if vals.is_new_eq_present() { | ||
| 0 | ||
| } else { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
part where we return zero is now here
revm/crates/interpreter/src/gas/params.rs
Lines 317 to 321 in 403f28f
| // If current value equals new value (this is a no-op) | |
| if vals.is_new_eq_present() { | |
| return 0; | |
| } | |
| if vals.is_original_eq_present() && vals.is_new_zero() { | ||
| sstore_clears_schedule | ||
| } else { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This part is not refactored here
revm/crates/interpreter/src/gas/params.rs
Lines 322 to 327 in 403f28f
| // refund for the clearing of storage slot. | |
| // As new is not equal to present, new values zero means that original and present values are not zero | |
| if vals.is_original_eq_present() && vals.is_new_zero() { | |
| return sstore_clearing_slot_refund; | |
| } | |
| if !vals.is_original_zero() { | ||
| if vals.is_present_zero() { | ||
| refund -= sstore_clears_schedule; | ||
| } else if vals.is_new_zero() { | ||
| refund += sstore_clears_schedule; | ||
| } | ||
| } | ||
|
|
||
| if vals.is_original_eq_new() { | ||
| let (gas_sstore_reset, gas_sload) = if spec_id.is_enabled_in(SpecId::BERLIN) { | ||
| (SSTORE_RESET - COLD_SLOAD_COST, WARM_STORAGE_READ_COST) | ||
| } else { | ||
| (SSTORE_RESET, sload_cost(spec_id, false)) | ||
| }; | ||
| if vals.is_original_zero() { | ||
| refund += (SSTORE_SET - gas_sload) as i64; | ||
| } else { | ||
| refund += (gas_sstore_reset - gas_sload) as i64; | ||
| } | ||
| } | ||
|
|
||
| refund |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
And last part is now here. Added additional comments from EIP
revm/crates/interpreter/src/gas/params.rs
Lines 328 to 355 in 403f28f
| let mut refund = 0; | |
| // If original value is not 0 | |
| if !vals.is_original_zero() { | |
| // If current value is 0 (also means that new value is not 0), | |
| if vals.is_present_zero() { | |
| // remove SSTORE_CLEARS_SCHEDULE gas from refund counter. | |
| refund -= sstore_clearing_slot_refund; | |
| // If new value is 0 (also means that current value is not 0), | |
| } else if vals.is_new_zero() { | |
| // add SSTORE_CLEARS_SCHEDULE gas to refund counter. | |
| refund += sstore_clearing_slot_refund; | |
| } | |
| } | |
| // If original value equals new value (this storage slot is reset) | |
| if vals.is_original_eq_new() { | |
| // If original value is 0 | |
| if vals.is_original_zero() { | |
| // add SSTORE_SET_GAS - SLOAD_GAS to refund counter. | |
| refund += self.sstore_set_without_load_cost() as i64; | |
| // Otherwise | |
| } else { | |
| // add SSTORE_RESET_GAS - SLOAD_GAS gas to refund counter. | |
| refund += self.sstore_reset_without_cold_load_cost() as i64; | |
| } | |
| } | |
| refund | |
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull Request Overview
This PR introduces a configurable gas parameters system (GasParams) for the EVM interpreter, enabling easy modification of dynamic gas costs for opcodes. The implementation replaces hardcoded gas calculations with a flexible, spec-aware table-based approach.
Key Changes:
- Introduced
GasParamsstruct with gas cost table that adapts based on EVM spec version - Refactored all instruction implementations to use
GasParamsinstead of direct gas constant references - Updated memory resize operations to accept and use
GasParams - Added gas parameter configuration in handler initialization
- Enhanced test script with
--keep-goingflag support
Reviewed Changes
Copilot reviewed 28 out of 28 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
scripts/run-tests.sh |
Added --keep-going flag support for continuing tests after failures |
crates/interpreter/src/gas/params.rs |
New core gas parameters implementation with configurable gas table |
crates/interpreter/src/interpreter.rs |
Integrated GasParams into interpreter struct and initialization |
crates/interpreter/src/interpreter/shared_memory.rs |
Updated memory resize to use GasParams and return Result |
crates/interpreter/src/instructions/*.rs |
Refactored all instruction gas calculations to use GasParams |
crates/interpreter/src/gas/*.rs |
Removed deprecated gas calculation functions, kept core utilities |
crates/handler/src/*.rs |
Updated handler to configure gas parameters based on spec |
crates/context/src/journal/inner.rs |
Minor type alias cleanup |
crates/context/interface/src/context.rs |
Added helper methods to SStoreResult |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
A way to configure all gas-related parameters in EVM so we can easily change dynamic part of gas of opcode.