ethereum.forks.bpo5.vm.interpreterethereum.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

65
MAX_CODE_SIZE = 0x6000
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. `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.
  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
    """
108
    block_env = message.block_env
123
    tx_state = message.tx_env.state
124
    refund_counter = U256(0)
125
    if message.target == Bytes0(b""):
111
        is_collision = account_has_code_or_nonce(
112
            block_env.state, message.current_target
113
        ) or account_has_storage(block_env.state, message.current_target)
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:
124
            evm = process_create_message(message)
142
            evm = process_message(message, True)
143
    else:
144
        if message.tx_env.authorizations != ():
127
            refund_counter += set_delegation(message)
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)
133
            message.code = get_code(
134
                block_env.state,
135
                get_account(block_env.state, delegated_address).code_hash,
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
139
        evm = process_message(message)
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
147
        refund_counter += U256(evm.refund_counter)
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
    )

process_create_message

Executes a call to create a smart contract.

Parameters

message : Transaction specific items.

Returns

evm: :py:class:~ethereum.forks.bpo5.vm.Evm Items containing execution specific objects.

def process_create_message(message: Message) -> Evm:
165
    """
166
    Executes a call to create a smart contract.
167
168
    Parameters
169
    ----------
170
    message :
171
        Transaction specific items.
172
173
    Returns
174
    -------
175
    evm: :py:class:`~ethereum.forks.bpo5.vm.Evm`
176
        Items containing execution specific objects.
177
178
    """
179
    state = message.block_env.state
180
    transient_storage = message.tx_env.transient_storage
181
    # take snapshot of state before processing the message
182
    begin_transaction(state, transient_storage)
183
184
    # The list of created accounts is used by `get_storage_original`.
185
    # Additionally, the list is needed to respect the constraints
186
    # added to SELFDESTRUCT by EIP-6780.
187
    mark_account_created(state, message.current_target)
188
189
    increment_nonce(state, message.current_target)
190
    evm = process_message(message)
191
    if not evm.error:
192
        contract_code = evm.output
193
        contract_code_gas = (
194
            ulen(contract_code) * GasCosts.CODE_DEPOSIT_PER_BYTE
195
        )
196
        try:
197
            if len(contract_code) > 0:
198
                if contract_code[0] == 0xEF:
199
                    raise InvalidContractPrefix
200
            charge_gas(evm, contract_code_gas)
201
            if len(contract_code) > MAX_CODE_SIZE:
202
                raise OutOfGasError
203
        except ExceptionalHalt as error:
204
            rollback_transaction(state, transient_storage)
205
            evm.gas_left = Uint(0)
206
            evm.output = b""
207
            evm.error = error
208
        else:
209
            set_code(state, message.current_target, contract_code)
210
            commit_transaction(state, transient_storage)
211
    else:
212
        rollback_transaction(state, transient_storage)
213
    return evm

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.bpo5.vm.Evm~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
    -------
227
    evm: :py:class:`~ethereum.forks.bpo5.vm.Evm`
294
    evm: :py:class:`~ethereum.forks.amsterdam.vm.Evm`
295
        Items containing execution specific objects
296
297
    """
231
    state = message.block_env.state
298
    tx_state = message.tx_env.state
299
    if message.depth > STACK_DEPTH_LIMIT:
300
        raise StackDepthLimitError("Stack depth limit reached")
301
235
    transient_storage = message.tx_env.transient_storage
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
258
    begin_transaction(state, transient_storage)
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:
261
        move_ether(
262
            state, message.caller, message.current_target, message.value
263
        )
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
282
            evm_trace(evm, EvmStop(Ops.STOP))
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)
287
        evm.output = b""
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:
290
        evm_trace(evm, OpException(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:
294
        # revert state to the last saved checkpoint
295
        # since the message call resulted in an error
296
        rollback_transaction(state, transient_storage)
297
    else:
298
        commit_transaction(state, transient_storage)
434
        restore_tx_state(tx_state, revert_snapshot)
435
    return evm