ethereum.forks.prague.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

63
STACK_DEPTH_LIMIT = Uint(1024)

MAX_CODE_SIZE

64
MAX_CODE_SIZE = 0x6000

MAX_INIT_CODE_SIZE

65
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.
68
@final
69
@dataclass
class MessageCallOutput:

gas_left

84
    gas_left: Uint

refund_counter

85
    refund_counter: U256

logs

86
    logs: Tuple[Log, ...]

accounts_to_delete

87
    accounts_to_delete: Set[Address]

error

88
    error: Optional[EthereumException]

return_data

89
    return_data: Bytes

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:
93
    <snip>
108
    tx_state = message.tx_env.state
109
    refund_counter = U256(0)
110
    if message.target == Bytes0(b""):
111
        if account_deployable(tx_state, message.current_target):
112
            evm = process_create_message(message)
113
        else:
114
            return MessageCallOutput(
115
                gas_left=Uint(0),
116
                refund_counter=U256(0),
117
                logs=tuple(),
118
                accounts_to_delete=set(),
119
                error=AddressCollision(),
120
                return_data=Bytes(b""),
121
            )
122
    else:
123
        if message.tx_env.authorizations != ():
124
            refund_counter += set_delegation(message)
125
126
        delegated_address = get_delegated_code_address(message.code)
127
        if delegated_address is not None:
128
            message.disable_precompiles = True
129
            message.accessed_addresses.add(delegated_address)
130
            message.code = get_code(
131
                tx_state,
132
                get_account(tx_state, delegated_address).code_hash,
133
            )
134
            message.code_address = delegated_address
135
136
        evm = process_message(message)
137
138
    if evm.error:
139
        logs: Tuple[Log, ...] = ()
140
        accounts_to_delete = set()
141
    else:
142
        logs = evm.logs
143
        accounts_to_delete = evm.accounts_to_delete
144
        refund_counter += U256(evm.refund_counter)
145
146
    tx_end = TransactionEnd(
147
        int(message.gas) - int(evm.gas_left), evm.output, evm.error
148
    )
149
    evm_trace(evm, tx_end)
150
151
    return MessageCallOutput(
152
        gas_left=evm.gas_left,
153
        refund_counter=refund_counter,
154
        logs=logs,
155
        accounts_to_delete=accounts_to_delete,
156
        error=evm.error,
157
        return_data=evm.output,
158
    )

process_create_message

Executes a call to create a smart contract.

Parameters

message : Transaction specific items.

Returns

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

def process_create_message(message: Message) -> Evm:
162
    <snip>
176
    tx_state = message.tx_env.state
177
    # take snapshot of state before processing the message
178
    snapshot = copy_tx_state(tx_state)
179
180
    # If the address where the account is being created has storage, it is
181
    # destroyed. This can only happen in the following highly unlikely
182
    # circumstances:
183
    # * The address created by a `CREATE` call collides with a subsequent
184
    #   `CREATE` or `CREATE2` call.
185
    # * The first `CREATE` happened before Spurious Dragon and left empty
186
    #   code.
187
    destroy_storage(tx_state, message.current_target)
188
189
    # In the previously mentioned edge case the preexisting storage is ignored
190
    # for gas refund purposes. In order to do this we must track created
191
    # accounts. This tracking is also needed to respect the constraints
192
    # added to SELFDESTRUCT by EIP-6780.
193
    mark_account_created(tx_state, message.current_target)
194
195
    increment_nonce(tx_state, message.current_target)
196
    evm = process_message(message)
197
    if not evm.error:
198
        contract_code = evm.output
199
        contract_code_gas = (
200
            ulen(contract_code) * GasCosts.CODE_DEPOSIT_PER_BYTE
201
        )
202
        try:
203
            if len(contract_code) > 0:
204
                if contract_code[0] == 0xEF:
205
                    raise InvalidContractPrefix
206
            charge_gas(evm, contract_code_gas)
207
            if len(contract_code) > MAX_CODE_SIZE:
208
                raise OutOfGasError
209
        except ExceptionalHalt as error:
210
            restore_tx_state(tx_state, snapshot)
211
            evm.gas_left = Uint(0)
212
            evm.output = b""
213
            evm.error = error
214
        else:
215
            set_code(tx_state, message.current_target, contract_code)
216
    else:
217
        restore_tx_state(tx_state, snapshot)
218
    return evm

process_message

Move ether and execute the relevant code.

Parameters

message : Transaction specific items.

Returns

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

def process_message(message: Message) -> Evm:
222
    <snip>
236
    tx_state = message.tx_env.state
237
    if message.depth > STACK_DEPTH_LIMIT:
238
        raise StackDepthLimitError("Stack depth limit reached")
239
240
    code = message.code
241
    valid_jump_destinations = get_valid_jump_destinations(code)
242
    evm = Evm(
243
        pc=Uint(0),
244
        stack=[],
245
        memory=bytearray(),
246
        code=code,
247
        gas_left=message.gas,
248
        valid_jump_destinations=valid_jump_destinations,
249
        logs=(),
250
        refund_counter=0,
251
        running=True,
252
        message=message,
253
        output=b"",
254
        accounts_to_delete=set(),
255
        return_data=b"",
256
        error=None,
257
        accessed_addresses=message.accessed_addresses,
258
        accessed_storage_keys=message.accessed_storage_keys,
259
    )
260
261
    # take snapshot of state before processing the message
262
    snapshot = copy_tx_state(tx_state)
263
264
    if message.should_transfer_value and message.value != 0:
265
        move_ether(
266
            tx_state,
267
            message.caller,
268
            message.current_target,
269
            message.value,
270
        )
271
272
    try:
273
        if evm.message.code_address in PRE_COMPILED_CONTRACTS:
274
            if not message.disable_precompiles:
275
                evm_trace(evm, PrecompileStart(evm.message.code_address))
276
                PRE_COMPILED_CONTRACTS[evm.message.code_address](evm)
277
                evm_trace(evm, PrecompileEnd())
278
        else:
279
            while evm.running and evm.pc < ulen(evm.code):
280
                try:
281
                    op = Ops(evm.code[evm.pc])
282
                except ValueError as e:
283
                    raise InvalidOpcode(evm.code[evm.pc]) from e
284
285
                evm_trace(evm, OpStart(op))
286
                op_implementation[op](evm)
287
                evm_trace(evm, OpEnd())
288
289
            evm_trace(evm, EvmStop(Ops.STOP))
290
291
    except ExceptionalHalt as error:
292
        evm_trace(evm, OpException(error))
293
        evm.gas_left = Uint(0)
294
        evm.output = b""
295
        evm.error = error
296
    except Revert as error:
297
        evm_trace(evm, OpException(error))
298
        evm.error = error
299
300
    if evm.error:
301
        restore_tx_state(tx_state, snapshot)
302
    return evm