-
Notifications
You must be signed in to change notification settings - Fork 414
Description
Description
EIP-7904, proposed for the upcoming fork, introduces a set of eips that will update gas costs with actual computational overhead for each opcode.
However, the current setup relies on two separate data sources for gas models, requiring engineers to update both systems manually each time gas values change.
Current Structure
EELS
Defined in src/ethereum/forks/{fork_name}/vm/gas.py -> Example
For each opcode, charge specific gas amount based on the opcode's gas category:
def add(evm: Evm) -> None:
x = pop(evm.stack)
y = pop(evm.stack)
charge_gas(evm, GAS_VERY_LOW) # ADD is under GAS_VERY_LOW category
result = x.wrapping_add(y)
push(evm.stack, result)
evm.pc += Uint(1)EEST
Defined in src/ethereum_spec_tests/ethereum_test_forks/forks/forks.py -> Example
In benchmark tests, this is used by hardcoded the value, similar to the EELS implementation here:
loop_cost = (
gas_costs.G_KECCAK_256 # KECCAK static cost
+ math.ceil(85 / 32) * gas_costs.G_KECCAK_256_WORD # KECCAK dynamic
# cost for CREATE2
+ gas_costs.G_VERY_LOW * 3 # ~MSTOREs+ADDs
+ gas_costs.G_COLD_ACCOUNT_ACCESS # CALL to self-destructing contract
+ gas_costs.G_SELF_DESTRUCT
+ 63 # ~Gluing opcodes
)Difference
The difference between the variables definition, their naming or related analysis could be found here.
Potential Issue
We currently classify gas costs into categories such as GAS_VERY_LOW and GAS_BASE. However, EIP-7904 does not merely reduce the gas cost within existing categories, it introduces an entirely new gas cost structure, including categories like BASE_OPCODE_COST and FAST_OPCODE_COST. Each opcode is reorganized under these new categories to better reflect its actual computational cost.
Proposed Solution
Create a base cost configuration file in yaml format, which includes fundamental gas definition and the gas cost model for each opcode:
static_costs:
GAS_JUMPDEST: 1
GAS_BASE: 2
GAS_VERY_LOW: 3
GAS_LOW: 5
...
opcode_costs:
ADD: GAS_VERY_LOW
MUL: GAS_LOW
SUB: GAS_VERY_LOW
DIV: GAS_LOW
SDIV: GAS_LOW
MOD: GAS_LOW
SMOD: GAS_LOW
ADDMOD: GAS_MID
MULMOD: GAS_MID
EXP: GAS_EXPONENTIATION # base cost
SIGNEXTEND: GAS_LOW
...
# System Operations
CREATE: GAS_CREATE # base cost, plus init code and memory costs
CALL: GAS_WARM_ACCESS # Variable based on call type and account access
CALLCODE: GAS_WARM_ACCESS # Variable based on call type and account access
RETURN: GAS_ZERO # No gas cost
CREATE2: GAS_CREATE # base cost, plus init code and memory costs
STATICCALL: GAS_WARM_ACCESS # Variable based on call type and account access
REVERT: GAS_ZERO # No gas cost
SELFDESTRUCT: GAS_SELF_DESTRUCT # base cost, plus account creation if neededNote that here it is only the base cost, excluding the cost for memory expansion, warm/code state access and other additional cost.
And we replace the current eels implementation like this:
# Import the gas cost defined in base_cost.yaml
def add(evm: Evm) -> None:
x = pop(evm.stack)
y = pop(evm.stack)
charge_gas(evm, base_cost.ADD) # Updated version
result = x.wrapping_add(y)
push(evm.stack, result)
evm.pc += Uint(1)Another example:
def mload(evm: Evm) -> None:
start_position = pop(evm.stack)
extend_memory = calculate_gas_extend_memory(
evm.memory, [(start_position, U256(32))]
)
charge_gas(evm, base_cost.MLOAD + extend_memory.cost) # only cover base cost here
evm.memory += b"\x00" * extend_memory.expand_by
value = U256.from_be_bytes(
memory_read_bytes(evm.memory, start_position, U256(32))
)
push(evm.stack, value)
evm.pc += Uint(1)With this definition, we could enable a fork-dependent gas calculator that helps implement this feature, so we could get rid of hardcoded values in benchmark tests.
opcode = Bytecode(Op.ADD(Op.MLOAD(0), 1))
fork.gas_cost_cal(opcode)Benefit
- During gas repricing iterations, other teams can use this as a reference for the latest gas cost model.
- Maintain a single unified gas definition across EELS and EEST, so any gas update only requires changes in one place.
- While the gas table itself may not be strictly necessary, it could help enable the gas calculator feature described here.
Note
- We can support this feature only starting from the Prague fork, as the current benchmark suite begins there.
- For the first iteration, we can focus on compute-intensive cases that do not involve complex gas cost models.
- This solution can also be limited to the gas-repricing branch, which is primarily used for iterative development.
This is just a draft that documented some initial idea and open to any discussion and feedback!