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¶
| 80 | STACK_DEPTH_LIMIT = Uint(1024) |
|---|
MAX_CODE_SIZE¶
| 81 | MAX_CODE_SIZE = 0x10000 |
|---|
MAX_INIT_CODE_SIZE¶
| 82 | 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. `refund_counter`: gas to refund after execution.
3. `logs`: list of `Log` generated during execution.
4. `accounts_to_delete`: Contracts which have self-destructed.
5. `error`: The error from the execution if any.
6. `return_data`: The output of the execution.
7. `state_gas_left`: remaining state gas after execution.
8. `state_gas_used`: State gas used during execution.
| 85 | @final |
|---|
| 86 | @dataclass |
|---|
class MessageCallOutput:
gas_left¶
| 103 | gas_left: Uint |
|---|
refund_counter¶
| 104 | refund_counter: U256 |
|---|
logs¶
| 105 | logs: Tuple[Log, ...] |
|---|
accounts_to_delete¶
| 106 | accounts_to_delete: Set[Address] |
|---|
error¶
| 107 | error: Optional[EthereumException] |
|---|
return_data¶
| 108 | return_data: Bytes |
|---|
state_gas_left¶
| 109 | state_gas_left: Uint |
|---|
state_gas_used¶
| 110 | 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:
| 114 | <snip> |
|---|---|
| 129 | tx_state = message.tx_env.state |
| 130 | if message.target == Bytes0(b""): |
| 131 | if account_deployable(tx_state, message.current_target): |
| 132 | evm = process_create_message(message) |
| 133 | else: |
| 134 | return MessageCallOutput( |
| 135 | gas_left=Uint(0), |
| 136 | refund_counter=U256(0), |
| 137 | logs=tuple(), |
| 138 | accounts_to_delete=set(), |
| 139 | error=AddressCollision(), |
| 140 | return_data=Bytes(b""), |
| 141 | state_gas_left=message.state_gas_reservoir, |
| 142 | state_gas_used=0, |
| 143 | ) |
| 144 | else: |
| 145 | # Authorizations and delegation resolution are handled at the |
| 146 | # top frame inside ``process_message`` (depth 0), so their |
| 147 | # state-dependent gas charges go through the EVM gas pools and |
| 148 | # an out-of-gas there halts the frame cleanly. |
| 149 | evm = process_message(message) |
| 150 | |
| 151 | if evm.error: |
| 152 | logs: Tuple[Log, ...] = () |
| 153 | accounts_to_delete = set() |
| 154 | else: |
| 155 | logs = evm.logs |
| 156 | accounts_to_delete = evm.accounts_to_delete |
| 157 | |
| 158 | tx_end = TransactionEnd( |
| 159 | int(message.gas) - int(evm.gas_meter.gas_left), evm.output, evm.error |
| 160 | ) |
| 161 | evm_trace(evm, tx_end) |
| 162 | |
| 163 | # A failed frame settles its meter with a zero refund counter, so |
| 164 | # the refunds can be read unconditionally. |
| 165 | return MessageCallOutput( |
| 166 | gas_left=evm.gas_meter.gas_left, |
| 167 | refund_counter=U256(evm.gas_meter.refund_counter), |
| 168 | logs=logs, |
| 169 | accounts_to_delete=accounts_to_delete, |
| 170 | error=evm.error, |
| 171 | return_data=evm.output, |
| 172 | state_gas_left=evm.gas_meter.state_gas_left, |
| 173 | state_gas_used=tx_state_gas_used( |
| 174 | evm.gas_meter, message.state_gas_reservoir |
| 175 | ), |
| 176 | ) |
process_create_message ¶
Executes a call to create a smart contract.
Parameters
message : Transaction specific items.
Returns
evm: :py:class:~ethereum.forks.amsterdam.vm.Evm
Items containing execution specific objects.
def process_create_message(message: Message) -> Evm:
| 180 | <snip> |
|---|---|
| 194 | tx_state = message.tx_env.state |
| 195 | # take snapshot of state before processing the message |
| 196 | snapshot = copy_tx_state(tx_state) |
| 197 | |
| 198 | # If the address where the account is being created has storage, it is |
| 199 | # destroyed. This can only happen in the following highly unlikely |
| 200 | # circumstances: |
| 201 | # * The address created by a `CREATE` call collides with a subsequent |
| 202 | # `CREATE` or `CREATE2` call. |
| 203 | # * The first `CREATE` happened before Spurious Dragon and left empty |
| 204 | # code. |
| 205 | destroy_storage(tx_state, message.current_target) |
| 206 | |
| 207 | # In the previously mentioned edge case the preexisting storage is ignored |
| 208 | # for gas refund purposes. In order to do this we must track created |
| 209 | # accounts. This tracking is also needed to respect the constraints |
| 210 | # added to SELFDESTRUCT by EIP-6780. |
| 211 | mark_account_created(tx_state, message.current_target) |
| 212 | |
| 213 | increment_nonce(tx_state, message.current_target) |
| 214 | |
| 215 | evm = process_message(message) |
| 216 | if not evm.error: |
| 217 | contract_code = evm.output |
| 218 | try: |
| 219 | if len(contract_code) > 0: |
| 220 | if contract_code[0] == 0xEF: |
| 221 | raise InvalidContractPrefix |
| 222 | if len(contract_code) > MAX_CODE_SIZE: |
| 223 | raise OutOfGasError |
| 224 | # Hash cost for computing keccak256 of deployed bytecode |
| 225 | code_hash_gas = ( |
| 226 | GasCosts.OPCODE_KECCAK256_PER_WORD |
| 227 | * ceil32(ulen(contract_code)) |
| 228 | // Uint(32) |
| 229 | ) |
| 230 | charge_gas(evm, code_hash_gas) |
| 231 | code_deposit_state_gas = ( |
| 232 | ulen(contract_code) * StateGasCosts.COST_PER_STATE_BYTE |
| 233 | ) |
| 234 | charge_state_gas(evm, code_deposit_state_gas) |
| 235 | except ExceptionalHalt as error: |
| 236 | restore_tx_state(tx_state, snapshot) |
| 237 | # A create frame never applies authorizations, so its |
| 238 | # baseline is still the frame's entry reservoir. |
| 239 | restore_state_gas(evm.gas_meter) |
| 240 | forfeit_remaining_gas(evm.gas_meter) |
| 241 | evm.output = b"" |
| 242 | evm.error = error |
| 243 | else: |
| 244 | set_code(tx_state, message.current_target, contract_code) |
| 245 | else: |
| 246 | restore_tx_state(tx_state, snapshot) |
| 247 | return evm |
prepare_dispatch ¶
Charge the state-dependent dispatch costs and resolve the code the top frame will run.
Runs at the top frame (depth 0), after any EIP-7702 authorizations
have been applied by set_delegation and before the call is
dispatched:
charges the
NEW_ACCOUNTstate gas for a contract creation whose target leaf does not yet exist, or for a value transfer to a recipient that is not yet alive; andresolves a delegation on the recipient, charging the warm or cold account access and pointing the frame at the delegated code.
The creation target is checked against the transaction pre-state:
process_create_message has already bumped the target's nonce
by the time this runs, so a live check would always see the
account. The recipient check is live, so an authority
materialized earlier in the transaction is not charged
NEW_ACCOUNT again.
This function must not mutate the transaction state. Every charge
here pays for state that only materializes inside the dispatched
frame and rolls back with it, so these charges stay refillable --
unlike the set_delegation charges, whose state outlives a
dispatch failure and whose gas the caller folds into the frame
baseline. The no-mutation rule is also what keeps the caller's
execution snapshot equal to the state at that fold.
Insufficient gas raises an ExceptionalHalt; the caller rolls
back the whole preparation -- including the applied authorizations
-- and halts the frame without dispatching.
def prepare_dispatch(evm: Evm) -> None:
| 251 | <snip> |
|---|---|
| 285 | message = evm.message |
| 286 | tx_state = message.tx_env.state |
| 287 | |
| 288 | if message.target == Bytes0(b""): |
| 289 | if ( |
| 290 | get_pre_state_account(tx_state, message.current_target) |
| 291 | == EMPTY_ACCOUNT |
| 292 | ): |
| 293 | charge_state_gas(evm, StateGasCosts.NEW_ACCOUNT) |
| 294 | else: |
| 295 | recipient = message.current_target |
| 296 | if message.value > U256(0) and not is_account_alive( |
| 297 | tx_state, recipient |
| 298 | ): |
| 299 | charge_state_gas(evm, StateGasCosts.NEW_ACCOUNT) |
| 300 | recipient_code = get_code( |
| 301 | tx_state, get_account(tx_state, recipient).code_hash |
| 302 | ) |
| 303 | delegated_address = get_delegated_code_address(recipient_code) |
| 304 | if delegated_address is not None: |
| 305 | if delegated_address in evm.accessed_addresses: |
| 306 | charge_gas(evm, GasCosts.WARM_ACCESS) |
| 307 | else: |
| 308 | charge_gas(evm, GasCosts.COLD_ACCOUNT_ACCESS) |
| 309 | evm.accessed_addresses.add(delegated_address) |
| 310 | |
| 311 | message.disable_precompiles = True |
| 312 | message.code_address = delegated_address |
| 313 | message.code = get_code( |
| 314 | tx_state, |
| 315 | get_account(tx_state, delegated_address).code_hash, |
| 316 | ) |
| 317 | else: |
| 318 | message.code = recipient_code |
process_message ¶
Move ether and execute the relevant code.
Parameters
message : Transaction specific items.
Returns
evm: :py:class:~ethereum.forks.amsterdam.vm.Evm
Items containing execution specific objects
def process_message(message: Message) -> Evm:
| 322 | <snip> |
|---|---|
| 336 | tx_state = message.tx_env.state |
| 337 | if message.depth > STACK_DEPTH_LIMIT: |
| 338 | raise StackDepthLimitError("Stack depth limit reached") |
| 339 | |
| 340 | evm = Evm( |
| 341 | pc=Uint(0), |
| 342 | stack=[], |
| 343 | memory=bytearray(), |
| 344 | code=Bytes(b""), |
| 345 | gas_meter=GasMeter( |
| 346 | gas_left=message.gas, |
| 347 | state_gas_left=message.state_gas_reservoir, |
| 348 | state_gas_baseline=message.state_gas_reservoir, |
| 349 | ), |
| 350 | valid_jump_destinations=set(), |
| 351 | logs=(), |
| 352 | running=True, |
| 353 | message=message, |
| 354 | output=b"", |
| 355 | accounts_to_delete=set(), |
| 356 | return_data=b"", |
| 357 | error=None, |
| 358 | accessed_addresses=message.accessed_addresses, |
| 359 | accessed_storage_keys=message.accessed_storage_keys, |
| 360 | ) |
| 361 | |
| 362 | if message.depth == Uint(0): |
| 363 | prep_snapshot = copy_tx_state(tx_state) |
| 364 | try: |
| 365 | if message.tx_env.authorizations != (): |
| 366 | set_delegation(evm) |
| 367 | # The applied delegations outlive a failure of the |
| 368 | # dispatched code, so their state gas is committed as |
| 369 | # non-refillable; a later failure restores only to the |
| 370 | # post-commit baseline. |
| 371 | commit_state_gas(evm.gas_meter) |
| 372 | prepare_dispatch(evm) |
| 373 | except ExceptionalHalt as error: |
| 374 | evm_trace(evm, OpException(error)) |
| 375 | restore_tx_state(tx_state, prep_snapshot) |
| 376 | # The rollback reverts any applied delegations, so the |
| 377 | # commit above is undone with it: roll state gas back to |
| 378 | # frame entry, refilling every state charge. |
| 379 | restore_state_gas_to_entry( |
| 380 | evm.gas_meter, message.state_gas_reservoir |
| 381 | ) |
| 382 | forfeit_remaining_gas(evm.gas_meter) |
| 383 | evm.error = error |
| 384 | return evm |
| 385 | |
| 386 | assert message.code is not None |
| 387 | evm.code = message.code |
| 388 | evm.valid_jump_destinations = get_valid_jump_destinations(message.code) |
| 389 | |
| 390 | snapshot = copy_tx_state(tx_state) |
| 391 | |
| 392 | # Execute message code and handle errors |
| 393 | try: |
| 394 | if message.should_transfer_value and message.value != 0: |
| 395 | move_ether( |
| 396 | tx_state, |
| 397 | message.caller, |
| 398 | message.current_target, |
| 399 | message.value, |
| 400 | ) |
| 401 | if message.caller != message.current_target: |
| 402 | emit_transfer_log( |
| 403 | evm, |
| 404 | message.caller, |
| 405 | message.current_target, |
| 406 | message.value, |
| 407 | ) |
| 408 | if evm.message.code_address in PRE_COMPILED_CONTRACTS: |
| 409 | if not message.disable_precompiles: |
| 410 | evm_trace(evm, PrecompileStart(evm.message.code_address)) |
| 411 | PRE_COMPILED_CONTRACTS[evm.message.code_address](evm) |
| 412 | evm_trace(evm, PrecompileEnd()) |
| 413 | else: |
| 414 | while evm.running and evm.pc < ulen(evm.code): |
| 415 | try: |
| 416 | op = Ops(evm.code[evm.pc]) |
| 417 | except ValueError as e: |
| 418 | raise InvalidOpcode(evm.code[evm.pc]) from e |
| 419 | |
| 420 | evm_trace(evm, OpStart(op)) |
| 421 | op_implementation[op](evm) |
| 422 | evm_trace(evm, OpEnd()) |
| 423 | |
| 424 | evm_trace(evm, EvmStop(Ops.STOP)) |
| 425 | |
| 426 | except ExceptionalHalt as error: |
| 427 | evm_trace(evm, OpException(error)) |
| 428 | # Frame settlement: refill state gas to the baseline, then |
| 429 | # forfeit -- a halted frame returns no regular gas to its |
| 430 | # parent. After these handlers the meter states exactly what |
| 431 | # the frame gives back, so parents absorb unconditionally. |
| 432 | restore_state_gas(evm.gas_meter) |
| 433 | forfeit_remaining_gas(evm.gas_meter) |
| 434 | evm.output = b"" |
| 435 | evm.error = error |
| 436 | except Revert as error: |
| 437 | evm_trace(evm, OpException(error)) |
| 438 | # Frame settlement: refill state gas to the baseline -- a |
| 439 | # reverted frame returns its unspent `gas_left` to its parent. |
| 440 | restore_state_gas(evm.gas_meter) |
| 441 | evm.error = error |
| 442 | |
| 443 | if evm.error: |
| 444 | restore_tx_state(tx_state, snapshot) |
| 445 | return evm |