ethereum.forks.amsterdam.vm.interpreter
Ethereum Virtual Machine (EVM) Interpreter.
.. contents:: Table of Contents :backlinks: none :local:
Introduction
A straightforward interpreter that executes EVM code.
STACK_DEPTH_LIMIT¶
| 72 | STACK_DEPTH_LIMIT = Uint(1024) |
|---|
MAX_CODE_SIZE¶
| 73 | MAX_CODE_SIZE = 0x8000 |
|---|
MAX_INIT_CODE_SIZE¶
| 74 | MAX_INIT_CODE_SIZE = 2 * MAX_CODE_SIZE |
|---|
MessageCallOutput ¶
Output of a particular message call.
Contains the following:
1. `gas_left`: remaining gas after execution.
2. `state_gas_reservoir`: remaining state gas reservoir after
execution.
3. `refund_counter`: gas to refund after execution.
4. `logs`: list of `Log` generated during execution.
5. `accounts_to_delete`: Contracts which have self-destructed.
6. `error`: The error from the execution if any.
7. `return_data`: The output of the execution.
8. `regular_gas_used`: Regular gas used during execution.
9. `state_gas_used`: State gas used during execution.
| 77 | @dataclass |
|---|
class MessageCallOutput:
gas_left¶
| 96 | gas_left: Uint |
|---|
state_gas_reservoir¶
| 97 | state_gas_reservoir: Uint |
|---|
refund_counter¶
| 98 | refund_counter: U256 |
|---|
logs¶
| 99 | logs: Tuple[Log, ...] |
|---|
accounts_to_delete¶
| 100 | accounts_to_delete: Set[Address] |
|---|
error¶
| 101 | error: Optional[EthereumException] |
|---|
return_data¶
| 102 | return_data: Bytes |
|---|
regular_gas_used¶
| 103 | regular_gas_used: Uint |
|---|
state_gas_used¶
| 104 | state_gas_used: int |
|---|
process_message_call ¶
If message.target is empty then it creates a smart contract
else it executes a call from the message.caller to the message.target.
Parameters
message : Transaction specific items.
Returns
output : MessageCallOutput
Output of the message call
def process_message_call(message: Message) -> MessageCallOutput:
| 108 | """ |
|---|---|
| 109 | If `message.target` is empty then it creates a smart contract |
| 110 | else it executes a call from the `message.caller` to the `message.target`. |
| 111 | |
| 112 | Parameters |
| 113 | ---------- |
| 114 | message : |
| 115 | Transaction specific items. |
| 116 | |
| 117 | Returns |
| 118 | ------- |
| 119 | output : `MessageCallOutput` |
| 120 | Output of the message call |
| 121 | |
| 122 | """ |
| 123 | tx_state = message.tx_env.state |
| 124 | refund_counter = U256(0) |
| 125 | if message.target == Bytes0(b""): |
| 126 | is_collision = account_has_code_or_nonce( |
| 127 | tx_state, message.current_target |
| 128 | ) or account_has_storage(tx_state, message.current_target) |
| 129 | if is_collision: |
| 130 | return MessageCallOutput( |
| 131 | gas_left=Uint(0), |
| 132 | state_gas_reservoir=Uint(0), |
| 133 | refund_counter=U256(0), |
| 134 | logs=tuple(), |
| 135 | accounts_to_delete=set(), |
| 136 | error=AddressCollision(), |
| 137 | return_data=Bytes(b""), |
| 138 | regular_gas_used=Uint(0), |
| 139 | state_gas_used=0, |
| 140 | ) |
| 141 | else: |
| 142 | evm = process_message(message, True) |
| 143 | else: |
| 144 | if message.tx_env.authorizations != (): |
| 145 | set_delegation(message) |
| 146 | |
| 147 | delegated_address = get_delegated_code_address(message.code) |
| 148 | if delegated_address is not None: |
| 149 | message.disable_precompiles = True |
| 150 | message.accessed_addresses.add(delegated_address) |
| 151 | message.code = get_code( |
| 152 | tx_state, |
| 153 | get_account(tx_state, delegated_address).code_hash, |
| 154 | ) |
| 155 | message.code_address = delegated_address |
| 156 | |
| 157 | evm = process_message(message, False) |
| 158 | |
| 159 | if evm.error: |
| 160 | logs: Tuple[Log, ...] = () |
| 161 | accounts_to_delete = set() |
| 162 | else: |
| 163 | logs = evm.logs |
| 164 | accounts_to_delete = evm.accounts_to_delete |
| 165 | refund_counter += U256(evm.refund_counter) |
| 166 | |
| 167 | # Deferred SELFDESTRUCT processing per EIP-8037 + EIP-6780. |
| 168 | # SELFDESTRUCT queues the originator into `accounts_to_delete` at |
| 169 | # opcode time; the actual `destroy_account` runs at the top-level |
| 170 | # frame to deduplicate (an account SELFDESTRUCTed multiple times |
| 171 | # is destroyed once) and to integrate with revert (child |
| 172 | # SELFDESTRUCTs in a reverted ancestor are dropped via |
| 173 | # `incorporate_child_on_error`). |
| 174 | # |
| 175 | # Per EIP-6780 only same-tx-created accounts are actually |
| 176 | # removed. The frame-end diff above charged for the create |
| 177 | # (account record, deployed code, new storage slots); refund |
| 178 | # those here directly to the reservoir (no 20% cap), then |
| 179 | # allow the loop in fork.py to destroy the account. |
| 180 | for address in evm.accounts_to_delete: |
| 181 | if address in tx_state.created_accounts: |
| 182 | account = get_account(tx_state, address) |
| 183 | code = get_code(tx_state, account.code_hash) |
| 184 | refund_bytes = int(StateCosts.NEW_ACCOUNT) + len(code) |
| 185 | for slot_value in tx_state.storage_writes.get( |
| 186 | address, {} |
| 187 | ).values(): |
| 188 | if slot_value != U256(0): |
| 189 | refund_bytes += int(StateCosts.STORAGE_SET) |
| 190 | refill_amount = refund_bytes * int(StateCosts.PER_BYTE) |
| 191 | # Per EIP-8037: refill `state_gas_reservoir` with |
| 192 | # the previously-charged state gas (account creation, |
| 193 | # code deposit, new storage slots), and decrease |
| 194 | # `execution_state_gas_used` by the same amount. The |
| 195 | # decrement may go negative because the account- |
| 196 | # creation portion was charged via |
| 197 | # `intrinsic_state_gas` (not via this counter); the |
| 198 | # negative offsets the intrinsic at block-level |
| 199 | # `tx_state_gas` accounting. |
| 200 | evm.state_gas_reservoir += Uint(refill_amount) |
| 201 | evm.state_gas_used -= refill_amount |
| 202 | |
| 203 | tx_end = TransactionEnd( |
| 204 | int(message.gas) - int(evm.gas_left), evm.output, evm.error |
| 205 | ) |
| 206 | evm_trace(evm, tx_end) |
| 207 | |
| 208 | return MessageCallOutput( |
| 209 | gas_left=evm.gas_left, |
| 210 | state_gas_reservoir=evm.state_gas_reservoir, |
| 211 | refund_counter=refund_counter, |
| 212 | logs=logs, |
| 213 | accounts_to_delete=accounts_to_delete, |
| 214 | error=evm.error, |
| 215 | return_data=evm.output, |
| 216 | regular_gas_used=evm.regular_gas_used, |
| 217 | state_gas_used=evm.state_gas_used, |
| 218 | ) |
apply_frame_state_gas ¶
Settle state-gas at a frame boundary.
Compute the signed byte delta between snapshot and the
current transaction state, multiply by CPSB to derive the
growth cost, and reconcile against this frame's reservoir.
already_paid is what successful descendants have already paid
for this subtree's net state growth, whether that charge came
from the reservoir or by spilling into gas_left. Subtracting it
gives the residual this frame still owes (or is owed back). When
a descendant over-credited the reservoir on a cross-frame
ephemeral, already_paid is negative, which flips this frame's
residual to positive and naturally cancels out the over-credit.
On out-of-gas, roll back to snapshot and mark evm.error.
def apply_frame_state_gas(evm: Evm, snapshot: TransactionState, message: Message) -> None:
| 224 | """ |
|---|---|
| 225 | Settle state-gas at a frame boundary. |
| 226 | |
| 227 | Compute the signed byte delta between ``snapshot`` and the |
| 228 | current transaction state, multiply by ``CPSB`` to derive the |
| 229 | growth cost, and reconcile against this frame's reservoir. |
| 230 | |
| 231 | `already_paid` is what successful descendants have already paid |
| 232 | for this subtree's net state growth, whether that charge came |
| 233 | from the reservoir or by spilling into `gas_left`. Subtracting it |
| 234 | gives the residual this frame still owes (or is owed back). When |
| 235 | a descendant over-credited the reservoir on a cross-frame |
| 236 | ephemeral, `already_paid` is negative, which flips this frame's |
| 237 | residual to positive and naturally cancels out the over-credit. |
| 238 | |
| 239 | On out-of-gas, roll back to ``snapshot`` and mark ``evm.error``. |
| 240 | """ |
| 241 | if evm.error: |
| 242 | return |
| 243 | tx_state = message.tx_env.state |
| 244 | byte_delta = compute_state_byte_diff(snapshot, tx_state) |
| 245 | growth_cost = byte_delta * int(StateCosts.PER_BYTE) |
| 246 | already_paid = evm.state_gas_used |
| 247 | this_call_cost = growth_cost - already_paid |
| 248 | |
| 249 | if this_call_cost > 0: |
| 250 | # Drain reservoir first; spill into `gas_left` if the |
| 251 | # reservoir is empty. Spillover is the backward-compat |
| 252 | # path: legacy txs that didn't separately budget for |
| 253 | # state gas can still succeed by burning regular gas. |
| 254 | cost = Uint(this_call_cost) |
| 255 | if evm.state_gas_reservoir >= cost: |
| 256 | evm.state_gas_reservoir -= cost |
| 257 | elif evm.state_gas_reservoir + evm.gas_left >= cost: |
| 258 | remainder = cost - evm.state_gas_reservoir |
| 259 | evm.state_gas_reservoir = Uint(0) |
| 260 | evm.gas_left -= remainder |
| 261 | else: |
| 262 | # Combined budget can't cover the growth → OOG. Roll |
| 263 | # back state changes; same semantics as a regular-gas |
| 264 | # OOG mid-frame. |
| 265 | restore_tx_state(tx_state, snapshot) |
| 266 | evm.error = OutOfGasError() |
| 267 | evm.output = b"" |
| 268 | if not evm.error: |
| 269 | evm.state_gas_used += int(cost) |
| 270 | elif this_call_cost < 0: |
| 271 | # Negative residual means this subtree net-shrunk state. |
| 272 | # Credit the reservoir directly; the credit is bounded by |
| 273 | # `already_paid` going negative when ancestors compose |
| 274 | # this frame's reservoir (their `this_call_cost` flips |
| 275 | # positive and recharges). |
| 276 | evm.state_gas_reservoir += Uint(-this_call_cost) |
process_message ¶
Move ether and execute the relevant code.
Parameters
message : Transaction specific items. create : Whether the message is contract-creating. include_account_creation_in_diff : Whether to include account creation in the diff to calculate state gas.
Returns
evm: :py:class:~ethereum.forks.amsterdam.vm.Evm
Items containing execution specific objects
def process_message(message: Message, create: bool) -> Evm:
| 280 | """ |
|---|---|
| 281 | Move ether and execute the relevant code. |
| 282 | |
| 283 | Parameters |
| 284 | ---------- |
| 285 | message : |
| 286 | Transaction specific items. |
| 287 | create : |
| 288 | Whether the message is contract-creating. |
| 289 | include_account_creation_in_diff : |
| 290 | Whether to include account creation in the diff to calculate state gas. |
| 291 | |
| 292 | Returns |
| 293 | ------- |
| 294 | evm: :py:class:`~ethereum.forks.amsterdam.vm.Evm` |
| 295 | Items containing execution specific objects |
| 296 | |
| 297 | """ |
| 298 | tx_state = message.tx_env.state |
| 299 | if message.depth > STACK_DEPTH_LIMIT: |
| 300 | raise StackDepthLimitError("Stack depth limit reached") |
| 301 | |
| 302 | code = message.code |
| 303 | valid_jump_destinations = get_valid_jump_destinations(code) |
| 304 | |
| 305 | evm = Evm( |
| 306 | pc=Uint(0), |
| 307 | stack=[], |
| 308 | memory=bytearray(), |
| 309 | code=code, |
| 310 | gas_left=message.gas, |
| 311 | state_gas_reservoir=message.state_gas_reservoir, |
| 312 | valid_jump_destinations=valid_jump_destinations, |
| 313 | logs=(), |
| 314 | refund_counter=0, |
| 315 | running=True, |
| 316 | message=message, |
| 317 | output=b"", |
| 318 | accounts_to_delete=set(), |
| 319 | return_data=b"", |
| 320 | error=None, |
| 321 | accessed_addresses=message.accessed_addresses, |
| 322 | accessed_storage_keys=message.accessed_storage_keys, |
| 323 | ) |
| 324 | |
| 325 | # take snapshot of state before processing the message |
| 326 | revert_snapshot = copy_tx_state(tx_state) |
| 327 | |
| 328 | if create: |
| 329 | # If the address where the account is being created has storage, it is |
| 330 | # destroyed. This can only happen in the following highly unlikely |
| 331 | # circumstances: |
| 332 | # * The address created by a `CREATE` call collides with a subsequent |
| 333 | # `CREATE` or `CREATE2` call. |
| 334 | # * The first `CREATE` happened before Spurious Dragon and left empty |
| 335 | # code. |
| 336 | destroy_storage(tx_state, message.current_target) |
| 337 | |
| 338 | # In the previously mentioned edge case the preexisting storage is |
| 339 | # ignored for gas refund purposes. In order to do this we must track |
| 340 | # created accounts. This tracking is also needed to respect the |
| 341 | # constraints added to SELFDESTRUCT by EIP-6780. |
| 342 | mark_account_created(tx_state, message.current_target) |
| 343 | |
| 344 | increment_nonce(tx_state, message.current_target) |
| 345 | |
| 346 | if message.should_transfer_value and message.value != 0: |
| 347 | # CALL-with-value to a non-existent address creates the |
| 348 | # target account when `move_ether` deposits the balance. The |
| 349 | # account creation surfaces in `tx_state.account_writes` and |
| 350 | # is picked up by `compute_state_byte_diff` at this frame's |
| 351 | # frame-end (or rolled back by `restore_tx_state` on error). |
| 352 | move_ether( |
| 353 | tx_state, |
| 354 | message.caller, |
| 355 | message.current_target, |
| 356 | message.value, |
| 357 | ) |
| 358 | # EIP-7708: Only emit transfer log to a different account |
| 359 | if message.caller != message.current_target: |
| 360 | emit_transfer_log( |
| 361 | evm, message.caller, message.current_target, message.value |
| 362 | ) |
| 363 | |
| 364 | diff_snapshot = copy_tx_state(tx_state) |
| 365 | |
| 366 | try: |
| 367 | if evm.message.code_address in PRE_COMPILED_CONTRACTS: |
| 368 | if not message.disable_precompiles: |
| 369 | evm_trace(evm, PrecompileStart(evm.message.code_address)) |
| 370 | PRE_COMPILED_CONTRACTS[evm.message.code_address](evm) |
| 371 | evm_trace(evm, PrecompileEnd()) |
| 372 | else: |
| 373 | while evm.running and evm.pc < ulen(evm.code): |
| 374 | try: |
| 375 | op = Ops(evm.code[evm.pc]) |
| 376 | except ValueError as e: |
| 377 | raise InvalidOpcode(evm.code[evm.pc]) from e |
| 378 | |
| 379 | evm_trace(evm, OpStart(op)) |
| 380 | op_implementation[op](evm) |
| 381 | evm_trace(evm, OpEnd()) |
| 382 | |
| 383 | evm_trace(evm, EvmStop(Ops.STOP)) |
| 384 | |
| 385 | if create: |
| 386 | contract_code = evm.output |
| 387 | if len(contract_code) > 0: |
| 388 | if contract_code[0] == 0xEF: |
| 389 | raise InvalidContractPrefix |
| 390 | if len(contract_code) > MAX_CODE_SIZE: |
| 391 | raise OutOfGasError |
| 392 | # The legacy per-byte code-deposit cost (200 gas/byte) is |
| 393 | # gone; deposited bytes now pay through the state-byte |
| 394 | # counter (`+len(contract_code)` below) at frame-end via |
| 395 | # `× PER_BYTE`. The only regular-gas cost retained for |
| 396 | # code deposit is the keccak256 hashing of the deployed |
| 397 | # bytecode, since that's a real per-word CPU cost the |
| 398 | # client incurs to derive `code_hash`. |
| 399 | code_hash_gas = ( |
| 400 | GasCosts.OPCODE_KECCACK256_PER_WORD |
| 401 | * ceil32(ulen(contract_code)) |
| 402 | // Uint(32) |
| 403 | ) |
| 404 | charge_gas(evm, code_hash_gas) |
| 405 | set_code(tx_state, message.current_target, contract_code) |
| 406 | |
| 407 | # Frame-end state-gas settlement. |
| 408 | apply_frame_state_gas(evm, diff_snapshot, message) |
| 409 | |
| 410 | except ExceptionalHalt as error: |
| 411 | evm_trace(evm, OpException(error)) |
| 412 | # On exceptional halt the frame consumes all remaining |
| 413 | # `gas_left`. Spill that into `regular_gas_used` so the |
| 414 | # parent's `incorporate_child_on_error` sees the full |
| 415 | # regular-gas burn (otherwise the forfeited remainder would |
| 416 | # be lost from the per-tx total). State gas isn't touched |
| 417 | # here — `incorporate_child_on_error` returns the child's |
| 418 | # state-gas budget (used + unused) to the parent reservoir, |
| 419 | # since the rolled-back state never grew. |
| 420 | evm.regular_gas_used += evm.gas_left |
| 421 | evm.gas_left = Uint(0) |
| 422 | # State gas is preserved on exceptional halt so it can be |
| 423 | # returned to the parent frame via incorporate_child_on_error. |
| 424 | evm.output = b"" |
| 425 | evm.error = error |
| 426 | except Revert as error: |
| 427 | # REVERT preserves remaining `gas_left` (refunded to the |
| 428 | # parent via `incorporate_child_on_error`); only the error |
| 429 | # is recorded here. |
| 430 | evm_trace(evm, OpException(error)) |
| 431 | evm.error = error |
| 432 | |
| 433 | if evm.error: |
| 434 | restore_tx_state(tx_state, revert_snapshot) |
| 435 | return evm |