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¶
| 84 | STACK_DEPTH_LIMIT = Uint(1024) |
|---|
MAX_CODE_SIZE¶
| 85 | MAX_CODE_SIZE = 0x10000 |
|---|
MAX_INIT_CODE_SIZE¶
| 86 | MAX_INIT_CODE_SIZE = 2 * MAX_CODE_SIZE |
|---|
TransactionOutput ¶
Settled output of a transaction's top-level call.
Carry the figures fee settlement and the receipt need, so the frame itself never leaves the interpreter.
| 89 | @final |
|---|
| 90 | @dataclass |
|---|
class TransactionOutput:
gas_left¶
Execution gas remaining after execution.
| 99 | gas_left: ExecutionGas |
|---|
refund_counter¶
Gas eligible for refund at the end of the transaction.
| 102 | refund_counter: U256 |
|---|
logs¶
Logs emitted during execution; empty when it failed.
| 105 | logs: Tuple[Log, ...] |
|---|
accounts_to_delete¶
Accounts self-destructed during execution; empty when it failed.
| 108 | accounts_to_delete: Set[Address] |
|---|
error¶
The error the execution halted with, if any.
| 111 | error: Optional[EthereumException] |
|---|
return_data¶
The output of the execution.
| 114 | return_data: Bytes |
|---|
state_gas_left¶
State gas remaining in the reservoir after execution.
| 117 | state_gas_left: StateGas |
|---|
state_gas_used¶
Net state gas consumed; negative when refunds exceed charges.
| 120 | state_gas_used: int |
|---|
charge_value_transfer_to_non_alive_account ¶
Charge the state gas for creating recipient when a value
transfer revives an account that is not alive.
def charge_value_transfer_to_non_alive_account(state: TransactionState, gas_meter: GasMeter, recipient: Address, value: U256) -> None:
| 130 | <snip> |
|---|---|
| 134 | if value > U256(0) and not is_account_alive(state, recipient): |
| 135 | charge_state_gas_from_meter(gas_meter, StateGasCosts.NEW_ACCOUNT) |
create_evm ¶
Build the transaction's top-level frame.
Apply the EIP-7702 authorizations, charge the state-dependent
dispatch costs to gas_meter, and resolve the code the frame
runs. A preparation failure -- a creation-address collision or
insufficient gas -- raises instead of building a frame, leaving
the caller to roll back the state and gas the preparation charged
and settle the transaction without dispatching.
def create_evm(block_env: BlockEnvironment, tx_env: TransactionEnvironment, gas_meter: GasMeter) -> Evm:
| 143 | <snip> |
|---|---|
| 153 | current_target = tx_env.recipient |
| 154 | if tx_env.is_create: |
| 155 | call_data = Bytes(b"") |
| 156 | else: |
| 157 | call_data = tx_env.data |
| 158 | |
| 159 | code_address: Optional[Address] = None |
| 160 | disable_precompiles = False |
| 161 | accessed_addresses: Set[Address] = set() |
| 162 | accessed_storage_keys = set(tx_env.access_list_storage_keys) |
| 163 | |
| 164 | ## Apply the 7702 delegations |
| 165 | if tx_env.authorizations != (): |
| 166 | accessed_authorities = set_delegation(block_env, tx_env, gas_meter) |
| 167 | accessed_addresses.update(accessed_authorities) |
| 168 | commit_state_gas(gas_meter) |
| 169 | |
| 170 | ## Warm up the access sets |
| 171 | accessed_addresses.add(block_env.coinbase) |
| 172 | accessed_addresses.update(PRE_COMPILED_CONTRACTS.keys()) |
| 173 | accessed_addresses.add(tx_env.origin) |
| 174 | accessed_addresses.update(tx_env.access_list_addresses) |
| 175 | accessed_addresses.add(current_target) |
| 176 | |
| 177 | ## Resolve dispatch and charge its state-dependent costs |
| 178 | if tx_env.is_create: |
| 179 | if not account_deployable(tx_env.state, current_target): |
| 180 | raise AddressCollision() |
| 181 | |
| 182 | if ( |
| 183 | get_pre_state_account(tx_env.state, current_target) |
| 184 | == EMPTY_ACCOUNT |
| 185 | ): |
| 186 | charge_state_gas_from_meter(gas_meter, StateGasCosts.NEW_ACCOUNT) |
| 187 | |
| 188 | code = tx_env.data |
| 189 | else: |
| 190 | charge_value_transfer_to_non_alive_account( |
| 191 | tx_env.state, gas_meter, current_target, tx_env.value |
| 192 | ) |
| 193 | |
| 194 | code_address, disable_precompiles = resolve_delegated_code_address( |
| 195 | tx_env.state, gas_meter, accessed_addresses, tx_env.recipient |
| 196 | ) |
| 197 | |
| 198 | code = get_code( |
| 199 | tx_env.state, |
| 200 | get_account(tx_env.state, code_address).code_hash, |
| 201 | ) |
| 202 | |
| 203 | ## Build the frame |
| 204 | return Evm( |
| 205 | # Context |
| 206 | block_env=block_env, |
| 207 | tx_env=tx_env, |
| 208 | parent_evm=None, |
| 209 | depth=Uint(0), |
| 210 | # Call Parameters |
| 211 | caller=tx_env.origin, |
| 212 | current_target=current_target, |
| 213 | value=tx_env.value, |
| 214 | call_data=call_data, |
| 215 | should_transfer_value=True, |
| 216 | is_static=False, |
| 217 | disable_precompiles=disable_precompiles, |
| 218 | # Code |
| 219 | code_address=code_address, |
| 220 | code=code, |
| 221 | valid_jump_destinations=get_valid_jump_destinations(code), |
| 222 | # Machine State |
| 223 | gas_meter=gas_meter, |
| 224 | pc=Uint(0), |
| 225 | stack=[], |
| 226 | memory=bytearray(), |
| 227 | return_data=b"", |
| 228 | # Accrued Effects |
| 229 | logs=(), |
| 230 | accounts_to_delete=set(), |
| 231 | accessed_addresses=accessed_addresses, |
| 232 | accessed_storage_keys=accessed_storage_keys, |
| 233 | # Outcome |
| 234 | running=True, |
| 235 | output=b"", |
| 236 | error=None, |
| 237 | ) |
process_top_level ¶
Execute the top level of a transaction.
Prepare the transaction's top-level EVM frame and dispatch it: a contract creation or a call, per the transaction environment. A preparation failure rolls back everything the preparation changed and never dispatches; the transaction then settles as if execution halted at entry, forfeiting its entire gas grant.
Parameters
block_env : Environment for the Ethereum Virtual Machine. tx_env : Environment for the transaction.
Returns
tx_output : TransactionOutput
The settled output of the top-level execution.
def process_top_level(block_env: BlockEnvironment, tx_env: TransactionEnvironment) -> TransactionOutput:
| 244 | <snip> |
|---|---|
| 266 | gas_meter = GasMeter( |
| 267 | gas_left=tx_env.execution_gas_grant, |
| 268 | state_gas_left=tx_env.state_gas_reservoir, |
| 269 | state_gas_baseline=tx_env.state_gas_reservoir, |
| 270 | ) |
| 271 | |
| 272 | prep_snapshot = copy_tx_state(tx_env.state) |
| 273 | try: |
| 274 | evm = create_evm(block_env, tx_env, gas_meter) |
| 275 | except ExceptionalHalt as halt: |
| 276 | # The rollback also reverts any applied delegations, so their |
| 277 | # state gas commit is undone with it: roll state gas back to |
| 278 | # frame entry, refilling every state charge. |
| 279 | restore_tx_state(tx_env.state, prep_snapshot) |
| 280 | restore_state_gas_to_entry(gas_meter, tx_env.state_gas_reservoir) |
| 281 | forfeit_remaining_gas(gas_meter) |
| 282 | return TransactionOutput( |
| 283 | gas_left=gas_meter.gas_left, |
| 284 | refund_counter=U256(gas_meter.refund_counter), |
| 285 | logs=(), |
| 286 | accounts_to_delete=set(), |
| 287 | error=halt, |
| 288 | return_data=Bytes(b""), |
| 289 | state_gas_left=gas_meter.state_gas_left, |
| 290 | state_gas_used=tx_state_gas_used( |
| 291 | gas_meter, tx_env.state_gas_reservoir |
| 292 | ), |
| 293 | ) |
| 294 | |
| 295 | if tx_env.is_create: |
| 296 | process_create(evm) |
| 297 | else: |
| 298 | process_call(evm) |
| 299 | |
| 300 | # A failed execution contributes no logs or self-destructs. |
| 301 | if evm.error: |
| 302 | logs: Tuple[Log, ...] = () |
| 303 | accounts_to_delete: Set[Address] = set() |
| 304 | else: |
| 305 | logs = evm.logs |
| 306 | accounts_to_delete = evm.accounts_to_delete |
| 307 | |
| 308 | tx_end = TransactionEnd( |
| 309 | int(tx_env.execution_gas_grant) - int(gas_meter.gas_left), |
| 310 | evm.output, |
| 311 | evm.error, |
| 312 | ) |
| 313 | evm_trace(evm, tx_end) |
| 314 | |
| 315 | return TransactionOutput( |
| 316 | gas_left=gas_meter.gas_left, |
| 317 | refund_counter=U256(gas_meter.refund_counter), |
| 318 | logs=logs, |
| 319 | accounts_to_delete=accounts_to_delete, |
| 320 | error=evm.error, |
| 321 | return_data=evm.output, |
| 322 | state_gas_left=gas_meter.state_gas_left, |
| 323 | state_gas_used=tx_state_gas_used( |
| 324 | gas_meter, tx_env.state_gas_reservoir |
| 325 | ), |
| 326 | ) |
process_create ¶
Executes a call to create a smart contract.
Parameters
evm : Currently running evm.
Returns
evm: :py:class:~ethereum.forks.amsterdam.vm.Evm
Items containing execution specific objects.
def process_create(evm: Evm) -> Evm:
| 330 | <snip> |
|---|---|
| 344 | tx_state = evm.tx_env.state |
| 345 | # take snapshot of state before processing the message |
| 346 | snapshot = copy_tx_state(tx_state) |
| 347 | |
| 348 | # If the address where the account is being created has storage, it is |
| 349 | # destroyed. This can only happen in the following highly unlikely |
| 350 | # circumstances: |
| 351 | # * The address created by a `CREATE` call collides with a subsequent |
| 352 | # `CREATE` or `CREATE2` call. |
| 353 | # * The first `CREATE` happened before Spurious Dragon and left empty |
| 354 | # code. |
| 355 | destroy_storage(tx_state, evm.current_target) |
| 356 | |
| 357 | # In the previously mentioned edge case the preexisting storage is ignored |
| 358 | # for gas refund purposes. In order to do this we must track created |
| 359 | # accounts. This tracking is also needed to respect the constraints |
| 360 | # added to SELFDESTRUCT by EIP-6780. |
| 361 | mark_account_created(tx_state, evm.current_target) |
| 362 | |
| 363 | increment_nonce(tx_state, evm.current_target) |
| 364 | |
| 365 | evm = process_call(evm) |
| 366 | if not evm.error: |
| 367 | contract_code = evm.output |
| 368 | try: |
| 369 | if len(contract_code) > 0: |
| 370 | if contract_code[0] == 0xEF: |
| 371 | raise InvalidContractPrefix |
| 372 | if len(contract_code) > MAX_CODE_SIZE: |
| 373 | raise OutOfGasError |
| 374 | # Hash cost for computing keccak256 of deployed bytecode |
| 375 | code_hash_gas = ExecutionGas( |
| 376 | GasCosts.OPCODE_KECCAK256_PER_WORD |
| 377 | * ceil32(ulen(contract_code)) |
| 378 | // Uint(32) |
| 379 | ) |
| 380 | charge_gas(evm, code_hash_gas) |
| 381 | code_deposit_state_gas = ( |
| 382 | ulen(contract_code) * StateGasCosts.COST_PER_STATE_BYTE |
| 383 | ) |
| 384 | charge_state_gas(evm, code_deposit_state_gas) |
| 385 | except ExceptionalHalt as error: |
| 386 | restore_tx_state(tx_state, snapshot) |
| 387 | # A create frame never applies authorizations, so its |
| 388 | # baseline is still the frame's entry reservoir. |
| 389 | restore_state_gas(evm.gas_meter) |
| 390 | forfeit_remaining_gas(evm.gas_meter) |
| 391 | evm.output = b"" |
| 392 | evm.error = error |
| 393 | else: |
| 394 | set_code(tx_state, evm.current_target, contract_code) |
| 395 | else: |
| 396 | restore_tx_state(tx_state, snapshot) |
| 397 | return evm |
process_call ¶
Move ether and execute the relevant code.
Parameters
evm : The EVM frame to execute.
Returns
evm: :py:class:~ethereum.forks.amsterdam.vm.Evm
Items containing execution specific objects
def process_call(evm: Evm) -> Evm:
| 401 | <snip> |
|---|---|
| 415 | tx_state = evm.tx_env.state |
| 416 | if evm.depth > STACK_DEPTH_LIMIT: |
| 417 | raise StackDepthLimitError("Stack depth limit reached") |
| 418 | |
| 419 | snapshot = copy_tx_state(tx_state) |
| 420 | |
| 421 | # Execute message code and handle errors |
| 422 | try: |
| 423 | if evm.should_transfer_value and evm.value != 0: |
| 424 | move_ether( |
| 425 | tx_state, |
| 426 | evm.caller, |
| 427 | evm.current_target, |
| 428 | evm.value, |
| 429 | ) |
| 430 | if evm.caller != evm.current_target: |
| 431 | emit_transfer_log( |
| 432 | evm, |
| 433 | evm.caller, |
| 434 | evm.current_target, |
| 435 | evm.value, |
| 436 | ) |
| 437 | if evm.code_address in PRE_COMPILED_CONTRACTS: |
| 438 | if not evm.disable_precompiles: |
| 439 | evm_trace(evm, PrecompileStart(evm.code_address)) |
| 440 | PRE_COMPILED_CONTRACTS[evm.code_address](evm) |
| 441 | evm_trace(evm, PrecompileEnd()) |
| 442 | else: |
| 443 | while evm.running and evm.pc < ulen(evm.code): |
| 444 | try: |
| 445 | op = Ops(evm.code[evm.pc]) |
| 446 | except ValueError as e: |
| 447 | raise InvalidOpcode(evm.code[evm.pc]) from e |
| 448 | |
| 449 | evm_trace(evm, OpStart(op)) |
| 450 | op_implementation[op](evm) |
| 451 | evm_trace(evm, OpEnd()) |
| 452 | |
| 453 | evm_trace(evm, EvmStop(Ops.STOP)) |
| 454 | |
| 455 | except ExceptionalHalt as error: |
| 456 | evm_trace(evm, OpException(error)) |
| 457 | # Frame settlement: refill state gas to the baseline, then |
| 458 | # forfeit -- a halted frame returns no execution gas to its |
| 459 | # parent. After these handlers the meter states exactly what |
| 460 | # the frame gives back, so parents absorb unconditionally. |
| 461 | restore_state_gas(evm.gas_meter) |
| 462 | forfeit_remaining_gas(evm.gas_meter) |
| 463 | evm.output = b"" |
| 464 | evm.error = error |
| 465 | except Revert as error: |
| 466 | evm_trace(evm, OpException(error)) |
| 467 | # Frame settlement: refill state gas to the baseline -- a |
| 468 | # reverted frame returns its unspent `gas_left` to its parent. |
| 469 | restore_state_gas(evm.gas_meter) |
| 470 | evm.error = error |
| 471 | |
| 472 | if evm.error: |
| 473 | restore_tx_state(tx_state, snapshot) |
| 474 | return evm |