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_ACCOUNT state 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; and

  • resolves 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