refactor(spec-specs): add opcode gas constants#2396
refactor(spec-specs): add opcode gas constants#2396Carsons-Eels wants to merge 45 commits intoethereum:forks/amsterdamfrom
Conversation
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## forks/amsterdam #2396 +/- ##
===================================================
+ Coverage 86.24% 86.43% +0.19%
===================================================
Files 599 599
Lines 36984 37515 +531
Branches 3795 3795
===================================================
+ Hits 31895 32426 +531
Misses 4525 4525
Partials 564 564
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
|
Awesome! cc @raxhvl, since this was directly mentioned in the gas-lighters call today. |
|
All failing tests are due to #2109, and unrelated to the changes in this PR: It should be ok for us to proceed to review -> merge. |
marioevz
left a comment
There was a problem hiding this comment.
Looks good to me, considering we are not touching opcodes that use GAS_BASE, which I'm guessing is because the amount of changes would grow even more, and it's not necessary for the gas repricings at the moment, we could leave it for a future PR if it becomes necessary IMO.
I just left a couple of comments and leave the approval to Sam when he returns.
SamWilsn
left a comment
There was a problem hiding this comment.
Looks good to me! Is the intent to make these constants modifiable at runtime? If so, there's some additional nonsense you need to do because Python.
bcf06ad to
4fb062e
Compare
Carsons-Eels
left a comment
There was a problem hiding this comment.
Regarding Louis' comment about constants in Frontier; I pushed a change that moves the 8 GAS_OPCODE_* fields that don't exist until after Frontier (SHL, SHR, SAR, RETURNDATACOPY, BLOBHASH, MCOPY, CLZ) to dataclass defaults and then initialize them via replace() where the fork introduces new terms. Frontier no longer references them.
I think we could do the same thing for the other 23 non-opcode constants that are zero-initialised in Frontier but aren't used until later forks:
Byzantium: GAS_PRECOMPILE_ECADD, GAS_PRECOMPILE_ECMUL, GAS_PRECOMPILE_ECPAIRING_BASE, GAS_PRECOMPILE_ECPAIRING_PER_POINT
Istanbul: GAS_PRECOMPILE_BLAKE2F_BASE, GAS_PRECOMPILE_BLAKE2F_PER_ROUND
Berlin: GAS_TX_ACCESS_LIST_ADDRESS, GAS_TX_ACCESS_LIST_STORAGE_KEY
Cancun: GAS_PRECOMPILE_POINT_EVALUATION
Prague: GAS_TX_DATA_TOKEN_STANDARD, GAS_TX_DATA_TOKEN_FLOOR, GAS_AUTH_PER_EMPTY_ACCOUNT, REFUND_AUTH_PER_EXISTING_ACCOUNT, GAS_PRECOMPILE_BLS_G1ADD, GAS_PRECOMPILE_BLS_G1MUL, GAS_PRECOMPILE_BLS_G1MAP, GAS_PRECOMPILE_BLS_G2ADD, GAS_PRECOMPILE_BLS_G2MUL, GAS_PRECOMPILE_BLS_G2MAP, GAS_PRECOMPILE_BLS_PAIRING_BASE, GAS_PRECOMPILE_BLS_PAIRING_PER_PAIR
Osaka: GAS_PRECOMPILE_P256VERIFY
Amsterdam: GAS_BLOCK_ACCESS_LIST_ITEM
None of these are referenced in Frontier, and they're already set by replace() by the fork that introduces them; they'd just also get = 0 defaults on the dataclass. Frontier doesn't need to mention them at all. This would follow along the same lines as the adjustment I made for Louis' comment, but that predates this PR, so I'd rather get feedback first. We're kind of already here though, so might as well if it feels right.
Thoughts? @LouisTsai-Csie @marioevz
9fa8094 to
1c9af18
Compare
|
opitonal nit: rename To ensure consistency with |
30525d8 to
e9f2c37
Compare
|
I updated |
SamWilsn
left a comment
There was a problem hiding this comment.
This is packages/** and src/ethereum/forks/amsterdam/**, and the occasional comment elsewhere.
| GAS_COLD_ACCOUNT_ACCESS = Uint(2600) | ||
| GAS_WARM_ACCESS = Uint(100) | ||
|
|
||
| # Opcode specific vars used for repricing |
There was a problem hiding this comment.
Some opcodes (eg. COINBASE) don't have their own constant. Is there a rule or pattern for determining when an opcode gets a constant?
There was a problem hiding this comment.
Not as such. I was thinking to refactor so that every opcode would have a corresponding constant, and the ones that are not opcodes themselves remain as is. Does that sound reasonable?
| GAS_BLOCK_ACCESS_LIST_ITEM: int | ||
|
|
||
| # Opcode specific gas constants for repricing | ||
| GAS_OPCODE_ADD: int |
There was a problem hiding this comment.
This ship has likely sailed, so feel free to disregard, but if we use GasCosts. as the prefix every time, we can probably drop GAS_ from the constants.
There was a problem hiding this comment.
That's a good point, the prefix definitely came from before I refactored.
SamWilsn
left a comment
There was a problem hiding this comment.
That's all of it, quickly read.
|
|
||
| # GAS | ||
| charge_gas(evm, GAS_BASE) | ||
| charge_gas(evm, GasCosts.GAS_BASE) |
There was a problem hiding this comment.
Missing opcode constant again perhaps?
| GAS_COLD_ACCOUNT_ACCESS = Uint(2600) | ||
| GAS_WARM_ACCESS = Uint(100) | ||
|
|
||
| # Opcode specific vars used for repricing |
…sulate Gas Constants in class
caefd35 to
c5c776e
Compare
| # Common Gas Cost Tiers | ||
| BASE = 2 | ||
| VERY_LOW = 3 | ||
| LOW = 5 | ||
| MID = 8 | ||
| HIGH = 10 |
There was a problem hiding this comment.
Do these names provide enough context, since they are accessed without a leading GasCosts.?
from execution_testing.forks.gas_costs import VERY_LOWThis would be the standard way to import the constant, and now VERY_LOW doesn't indicate anything about gas.
I'm happy either way, but wanted to ask.
| # Defined post-Frontier | ||
| OPCODE_SHL: int = 0 | ||
| OPCODE_SHR: int = 0 | ||
| OPCODE_SAR: int = 0 | ||
| OPCODE_RETURNDATACOPY: int = 0 | ||
| OPCODE_BLOBHASH: int = 0 | ||
| OPCODE_MCOPY: int = 0 | ||
| OPCODE_CLZ: int = 0 |
There was a problem hiding this comment.
Using zero as a sentinel for "not present" could cause issues, because zero is also (obviously) a valid integer. I can foresee a scenario where we accidentally assume OPCODE_SHL is present where it was set to zero, mypy wouldn't be able to detect it, and we end up with a zero cost operation. Using int | None would force developers to assert at the time of use.
That said, if this is already how the code works, changing it would be out of scope for this PR and this should be addressed in a separate issue.
|
|
||
| # GAS | ||
| charge_gas(evm, GAS_BASE) | ||
| charge_gas(evm, GasCosts.BASE) |
There was a problem hiding this comment.
Why isn't this GasCosts.OPCODE_COINBASE?
|
|
||
| # GAS | ||
| charge_gas(evm, GAS_BASE) | ||
| charge_gas(evm, GasCosts.BASE) |
There was a problem hiding this comment.
| charge_gas(evm, GasCosts.BASE) | |
| charge_gas(evm, GasCosts.OPCODE_TIMESTAMP) |
Similarly the rest of these.
Or did we discuss that but I've forgotten?
| if address in evm.accessed_addresses: | ||
| charge_gas(evm, GasCosts.WARM_ACCESS) | ||
| else: | ||
| evm.accessed_addresses.add(address) | ||
|
|
||
| charge_gas(evm, gas_cost) | ||
| charge_gas(evm, GasCosts.COLD_ACCOUNT_ACCESS) |
There was a problem hiding this comment.
This is much nicer looking.
| # GAS | ||
| words = ceil32(Uint(size)) // Uint(32) | ||
| copy_gas_cost = GAS_RETURN_DATA_COPY * words | ||
| copy_gas_cost = GasCosts.RETURN_DATA_COPY * words |
There was a problem hiding this comment.
When does a constant get _PER_WORD?
| class GasCosts: | ||
| """ | ||
| Constant gas values for the EVM. | ||
|
|
||
| These values may be patched at runtime by a future gas repricing utility | ||
| """ |
There was a problem hiding this comment.
| class GasCosts: | |
| """ | |
| Constant gas values for the EVM. | |
| These values may be patched at runtime by a future gas repricing utility | |
| """ | |
| # These constants are in a class so they can be patched at runtime. | |
| class GasCosts: | |
| """ | |
| Gas value constants for the EVM. | |
| """ |
I'd put the note about patching into a comment, since it isn't relevant to someone reading the specification; it's more of a note to other developers.
|
|
||
| # Utility | ||
| ZERO = Uint(0) | ||
| RETURN_DATA_COPY = Uint(3) # TODO refactor to OPCODE_RETURNDATACOPY? |
There was a problem hiding this comment.
Put longer lived TODOs in issues, not in code.
| OPCODE_SELFDESTRUCT_BASE = Uint(5000) | ||
|
|
||
| # TODO | ||
| MEMORY = Uint(3) |
There was a problem hiding this comment.
Should this have a _PER_WORD suffix?
🗒️ Description
Currently, multiple opcodes share the same gas tier constant (e.g. DIV, SDIV, MOD, SMOD, MUL all reference
GAS_LOW = Uint(5)). This makes it impossible to reprice individual opcodes because changingGAS_LOWwould affect all of them at once. EIP-7904 - General Repricing requires per-opcode repricing (e.g. DIV=15, SDIV=20, MOD=12 while MUL stays at 5).This PR adds per-opcode gas constants (
GAS_OPCODE_*) to all 24 forkgas.pymodules and updates instruction files to use them instead of shared tie constants (GAS_VERY_LOW,GAS_LOW,GAS_MID,GAS_HIGH), and likewise makes the same changes to the test code by adding counterpart gas constants to the GasCosts dataclass.This PR introduces intermediate variables like
GAS_OPCODE_DIV = GAS_LOWin each fork'sgas.pyand updates instruction files to charge against the opcode-specific constant. The existingapply_spec_repricing()mechanism (from #2331) can then targetGAS_OPCODE_DIVdirectly in the JSON config, enabling per-opcode repricing without changing spec code.Additionally,
GAS_BLOBHASH_OPCODEis renamed toGAS_BLOBHASHacross 9 forks (cancun+) to align with the existing gas constant naming convention (GAS_EXPONENTIATION,GAS_KECCAK256, etc.) and maintain a clean separation from the newGAS_OPCODE_*repricing variables.What changed:
Spec side:
gas.pyfiles: added 30–38GAS_OPCODE_*variable definitions (varying by fork based on available opcodes)charge_gas()calls to use opcode-specific constantsGAS_BLOBHASH_OPCODE→GAS_BLOBHASHTesting side:
gas_costs.py: added 39GAS_OPCODE_*fields toGasCostsdataclassforks.py: updatedFrontier.gas_costs()initialization andopcode_gas_map()in Frontier + 4 fork overrides (Byzantium, Constantinople, Cancun, Osaka) to reference per-opcode fields194 files total, no behavioural change — all variables default to their original tier constant values
Fork groups by opcode availability:
🔗 Related Issues or PRs
✅ Checklist
toxchecks to avoid unnecessary CI fails, see also Code Standards and Enabling Pre-commit Checks:uvx tox -e statictype(scope):.apply labels).
type(scope):.