Skip to content

Commit

Permalink
feat: process_create_message (#527)
Browse files Browse the repository at this point in the history
Closes #522
  • Loading branch information
enitrat authored Jan 23, 2025
1 parent cb9aa30 commit 91f6e39
Show file tree
Hide file tree
Showing 3 changed files with 214 additions and 71 deletions.
260 changes: 191 additions & 69 deletions cairo/ethereum/cancun/vm/interpreter.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -8,27 +8,211 @@ from starkware.cairo.common.math_cmp import is_nn
from ethereum_types.bytes import Bytes, BytesStruct
from ethereum_types.numeric import Uint, bool
from ethereum.cancun.blocks import TupleLog, TupleLogStruct, Log
from ethereum.cancun.vm.gas import GasConstants, charge_gas
from ethereum.cancun.fork_types import SetAddress
from ethereum.cancun.fork_types import SetAddressStruct, SetAddressDictAccess
from ethereum.cancun.vm import Evm, EvmStruct, Message, Environment, EvmImpl, EnvImpl
from ethereum.cancun.vm.exceptions import EthereumException, StackDepthLimitError, Revert
from ethereum.cancun.vm.exceptions import (
EthereumException,
InvalidContractPrefix,
OutOfGasError,
StackDepthLimitError,
Revert,
)
from ethereum.cancun.vm.instructions import op_implementation
from ethereum.cancun.vm.memory import Memory, MemoryStruct, Bytes1DictAccess
from ethereum.cancun.vm.runtime import get_valid_jump_destinations
from ethereum.cancun.vm.stack import Stack, StackStruct, StackDictAccess
from ethereum.utils.numeric import U256, U256Struct, U256__eq__
from ethereum.cancun.state import (
State,
begin_transaction,
destroy_storage,
mark_account_created,
increment_nonce,
commit_transaction,
rollback_transaction,
touch_account,
move_ether,
touch_account,
set_code,
)
from ethereum.cancun.vm.instructions import op_implementation
from ethereum.cancun.vm.memory import Memory, MemoryStruct, Bytes1DictAccess
from ethereum.cancun.vm.runtime import get_valid_jump_destinations
from ethereum.cancun.vm.stack import Stack, StackStruct, StackDictAccess
from ethereum.utils.numeric import U256, U256Struct, U256__eq__

from src.utils.dict import dict_new_empty

const STACK_DEPTH_LIMIT = 1024;
const MAX_CODE_SIZE = 0x6000;

func process_create_message{
range_check_ptr,
bitwise_ptr: BitwiseBuiltin*,
keccak_ptr: KeccakBuiltin*,
poseidon_ptr: PoseidonBuiltin*,
}(message: Message, env: Environment) -> (Evm, EthereumException*) {
alloc_locals;

let state = env.value.state;
let transient_storage = env.value.transient_storage;

// take snapshot of state before processing the message
begin_transaction{state=state, transient_storage=transient_storage}();

// If the address where the account is being created has storage, it is
// destroyed. This can only happen in the following highly unlikely
// circumstances:
// * The address created by a `CREATE` call collides with a subsequent
// `CREATE` or `CREATE2` call.
// * The first `CREATE` happened before Spurious Dragon and left empty
// code.
destroy_storage{state=state}(message.value.current_target);

// In the previously mentioned edge case the preexisting storage is ignored
// for gas refund purposes. In order to do this we must track created
// accounts.
mark_account_created{state=state}(message.value.current_target);

increment_nonce{state=state}(message.value.current_target);
EnvImpl.set_state{env=env}(state);
EnvImpl.set_transient_storage{env=env}(transient_storage);
let (evm, err) = process_message(message, env);
if (cast(err, felt) != 0) {
return (evm, err);
}

if (cast(evm.value.error, felt) != 0) {
// Error case
let env = evm.value.env;
let state = env.value.state;
rollback_transaction{state=state, transient_storage=transient_storage}();
EnvImpl.set_state{env=env}(state);
EnvImpl.set_transient_storage{env=env}(transient_storage);
EvmImpl.set_env{evm=evm}(env);

tempvar ok = cast(0, EthereumException*);
return (evm, ok);
}

let contract_code = evm.value.output;
let contract_code_gas = Uint(contract_code.value.len * GasConstants.GAS_CODE_DEPOSIT);

if (contract_code.value.len != 0) {
let first_opcode = contract_code.value.data[0];
if (first_opcode == 0xEF) {
tempvar err = new EthereumException(InvalidContractPrefix);
_process_create_message_error{evm=evm}(err);
tempvar ok = cast(0, EthereumException*);
return (evm, ok);
}
}

let err = charge_gas{evm=evm}(contract_code_gas);
if (cast(err, felt) != 0) {
_process_create_message_error{evm=evm}(err);
tempvar ok = cast(0, EthereumException*);
return (evm, ok);
}

let is_max_code_size_exceeded = is_nn(contract_code.value.len - MAX_CODE_SIZE);
if (is_max_code_size_exceeded != FALSE) {
tempvar err = new EthereumException(OutOfGasError);
_process_create_message_error{evm=evm}(err);
tempvar ok = cast(0, EthereumException*);
return (evm, ok);
}

let env = evm.value.env;
let state = env.value.state;
set_code{state=state}(message.value.current_target, contract_code);
commit_transaction{state=state, transient_storage=transient_storage}();
EnvImpl.set_state{env=env}(state);
EnvImpl.set_transient_storage{env=env}(transient_storage);
EvmImpl.set_env{evm=evm}(env);
tempvar ok = cast(0, EthereumException*);
return (evm, ok);
}

func _process_create_message_error{
range_check_ptr,
bitwise_ptr: BitwiseBuiltin*,
keccak_ptr: KeccakBuiltin*,
poseidon_ptr: PoseidonBuiltin*,
evm: Evm,
}(error: EthereumException*) {
let env = evm.value.env;
let state = env.value.state;
let transient_storage = env.value.transient_storage;
rollback_transaction{state=state, transient_storage=transient_storage}();
EnvImpl.set_state{env=env}(state);
EnvImpl.set_transient_storage{env=env}(transient_storage);
EvmImpl.set_env(env);
EvmImpl.set_gas_left(Uint(0));
let output_bytes: felt* = alloc();
tempvar output = Bytes(new BytesStruct(output_bytes, 0));
EvmImpl.set_output(output);
EvmImpl.set_error(error);
return ();
}

func process_message{
range_check_ptr,
bitwise_ptr: BitwiseBuiltin*,
keccak_ptr: KeccakBuiltin*,
poseidon_ptr: PoseidonBuiltin*,
}(message: Message, env: Environment) -> (Evm, EthereumException*) {
alloc_locals;

// Check if depth exceeds limit by checking if (depth - limit) is non-negative
let is_depth_exceeded = is_nn(message.value.depth.value - STACK_DEPTH_LIMIT);
if (is_depth_exceeded != FALSE) {
tempvar err = new EthereumException(StackDepthLimitError);
tempvar evm = Evm(cast(0, EvmStruct*));
return (evm, err);
}

// Take snapshot of state before processing the message
let state = env.value.state;
let transient_storage = env.value.transient_storage;
begin_transaction{state=state, transient_storage=transient_storage}();

// Touch account
touch_account{state=state}(message.value.current_target);

// Handle value transfer if needed
let value_eq_zero = U256__eq__(message.value.value, U256(new U256Struct(0, 0)));
let should_move_ether = message.value.should_transfer_value.value * (1 - value_eq_zero.value);
if (should_move_ether != FALSE) {
move_ether{state=state}(
message.value.caller, message.value.current_target, message.value.value
);
tempvar state = state;
tempvar range_check_ptr = range_check_ptr;
tempvar poseidon_ptr = poseidon_ptr;
} else {
tempvar state = state;
tempvar range_check_ptr = range_check_ptr;
tempvar poseidon_ptr = poseidon_ptr;
}

// Execute the code
EnvImpl.set_state{env=env}(state);
EnvImpl.set_transient_storage{env=env}(transient_storage);
let evm = execute_code(message, env);

// Handle transaction state based on execution result
let env = evm.value.env;
let state = env.value.state;
let transient_storage = env.value.transient_storage;
if (cast(evm.value.error, felt) != 0) {
rollback_transaction{state=state, transient_storage=transient_storage}();
} else {
commit_transaction{state=state, transient_storage=transient_storage}();
}
EnvImpl.set_state{env=env}(state);
EnvImpl.set_transient_storage{env=env}(transient_storage);
EvmImpl.set_env{evm=evm}(env);

let ok = cast(0, EthereumException*);
return (evm, ok);
}

func execute_code{
range_check_ptr,
Expand Down Expand Up @@ -150,65 +334,3 @@ func _execute_code{
// Recursive call to continue execution
return _execute_code(evm);
}

func process_message{
range_check_ptr,
bitwise_ptr: BitwiseBuiltin*,
keccak_ptr: KeccakBuiltin*,
poseidon_ptr: PoseidonBuiltin*,
}(message: Message, env: Environment) -> (Evm, EthereumException*) {
alloc_locals;

// Check if depth exceeds limit by checking if (depth - limit) is non-negative
let is_depth_exceeded = is_nn(message.value.depth.value - STACK_DEPTH_LIMIT);
if (is_depth_exceeded != FALSE) {
tempvar err = new EthereumException(StackDepthLimitError);
tempvar evm = Evm(cast(0, EvmStruct*));
return (evm, err);
}

// Take snapshot of state before processing the message
let state = env.value.state;
let transient_storage = env.value.transient_storage;
begin_transaction{state=state, transient_storage=transient_storage}();

// Touch account
touch_account{state=state}(message.value.current_target);

// Handle value transfer if needed
let value_eq_zero = U256__eq__(message.value.value, U256(new U256Struct(0, 0)));
let should_move_ether = message.value.should_transfer_value.value * (1 - value_eq_zero.value);
if (should_move_ether != FALSE) {
move_ether{state=state}(
message.value.caller, message.value.current_target, message.value.value
);
tempvar state = state;
tempvar range_check_ptr = range_check_ptr;
tempvar poseidon_ptr = poseidon_ptr;
} else {
tempvar state = state;
tempvar range_check_ptr = range_check_ptr;
tempvar poseidon_ptr = poseidon_ptr;
}

// Execute the code
EnvImpl.set_state{env=env}(state);
EnvImpl.set_transient_storage{env=env}(transient_storage);
let evm = execute_code(message, env);

// Handle transaction state based on execution result
let env = evm.value.env;
let state = env.value.state;
let transient_storage = env.value.transient_storage;
if (cast(evm.value.error, felt) != 0) {
rollback_transaction{state=state, transient_storage=transient_storage}();
} else {
commit_transaction{state=state, transient_storage=transient_storage}();
}
EnvImpl.set_state{env=env}(state);
EnvImpl.set_transient_storage{env=env}(transient_storage);
EvmImpl.set_env{evm=evm}(env);

let ok = cast(0, EthereumException*);
return (evm, ok);
}
23 changes: 22 additions & 1 deletion cairo/tests/ethereum/cancun/vm/test_interpreter.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,11 @@

from ethereum.cancun.fork_types import Address
from ethereum.cancun.vm import Environment, Message
from ethereum.cancun.vm.interpreter import execute_code, process_message
from ethereum.cancun.vm.interpreter import (
execute_code,
process_create_message,
process_message,
)
from tests.utils.errors import strict_raises
from tests.utils.message_builder import MessageBuilder
from tests.utils.strategies import environment_lite
Expand Down Expand Up @@ -60,3 +64,20 @@ def test_process_message(self, cairo_run, message: Message, env: Environment):

evm_python = process_message(message, env)
assert evm_python == evm_cairo

@given(
message=message_without_precompile,
env=environment_lite,
)
def test_process_create_message(
self, cairo_run, message: Message, env: Environment
):
try:
evm_cairo = cairo_run("process_create_message", message, env)
except Exception as e:
with strict_raises(type(e)):
process_create_message(message, env)
return

evm_python = process_create_message(message, env)
assert evm_python == evm_cairo
2 changes: 1 addition & 1 deletion cairo/tests/utils/strategies.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@
bloom = bytes256.map(Bloom)

excess_blob_gas = st.integers(min_value=0, max_value=MAX_BLOB_GAS_PER_BLOCK * 2).map(
Uint
U64
)

# Maximum recursion depth for the recursive strategy to avoid heavy memory usage and health check errors
Expand Down

0 comments on commit 91f6e39

Please sign in to comment.