Skip to content

Commit

Permalink
feat: incorporate child
Browse files Browse the repository at this point in the history
  • Loading branch information
enitrat committed Jan 24, 2025
1 parent cb9aa30 commit 553510e
Show file tree
Hide file tree
Showing 8 changed files with 524 additions and 6 deletions.
326 changes: 325 additions & 1 deletion cairo/ethereum/cancun/vm.cairo
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
from ethereum.cancun.blocks import Log, TupleLog
from starkware.cairo.common.cairo_builtins import PoseidonBuiltin
from ethereum.cancun.blocks import Log, TupleLog, TupleLogStruct
from ethereum.cancun.fork_types import (
Address,
ListHash32,
SetAddress,
SetAddressStruct,
SetAddressDictAccess,
TupleAddressBytes32,
SetTupleAddressBytes32,
SetTupleAddressBytes32Struct,
SetTupleAddressBytes32DictAccess,
TupleVersionedHash,
VersionedHash,
)
Expand All @@ -14,7 +19,19 @@ from ethereum_types.bytes import Bytes, Bytes0, Bytes32
from ethereum_types.numeric import U64, U256, Uint, bool, SetUint
from ethereum.cancun.transactions import To
from ethereum.cancun.vm.stack import Stack
from ethereum.cancun.state import account_exists_and_is_empty
from ethereum.cancun.vm.memory import Memory
from ethereum.utils.numeric import is_zero
from starkware.cairo.common.memcpy import memcpy
from src.utils.dict import (
hashdict_write,
hashdict_read,
dict_update,
squash_and_update,
dict_squash,
)
from starkware.cairo.common.registers import get_fp_and_pc
from starkware.cairo.common.dict import DictAccess

using OptionalEthereumException = EthereumException*;
using OptionalEvm = Evm;
Expand Down Expand Up @@ -88,6 +105,313 @@ struct MessageStruct {
parent_evm: OptionalEvm,
}

func incorporate_child_on_success{range_check_ptr, poseidon_ptr: PoseidonBuiltin*, evm: Evm}(
child_evm: Evm
) {
alloc_locals;

// TODO: unless we want to retro-prove all blocks, we could remove this logic.
// In block 2675119, the empty account at 0x3 (the RIPEMD160 precompile) was
// cleared despite running out of gas. This is an obscure edge case that can
// only happen to a precompile.
// According to the general rules governing clearing of empty accounts, the
// touch should have been reverted. Due to client bugs, this event went
// unnoticed and 0x3 has been exempted from the rule that touches are
// reverted in order to preserve this historical behaviour.

let fp_and_pc = get_fp_and_pc();
local __fp__: felt* = fp_and_pc.fp_val;

let new_gas_left = Uint(evm.value.gas_left.value + child_evm.value.gas_left.value);

let dst = evm.value.logs.value.data + evm.value.logs.value.len;
let src = child_evm.value.logs.value.data;
let len = child_evm.value.logs.value.len;
memcpy(dst, src, len);
tempvar new_logs = TupleLog(
new TupleLogStruct(data=evm.value.logs.value.data, len=evm.value.logs.value.len + len)
);

let new_refund_counter = evm.value.refund_counter + child_evm.value.refund_counter;

// Squash & update accounts_to_delete into parent
let accounts_to_delete = evm.value.accounts_to_delete;
let accounts_to_delete_start = accounts_to_delete.value.dict_ptr_start;
let accounts_to_delete_end = accounts_to_delete.value.dict_ptr;
let new_accounts_to_delete_end = squash_and_update(
cast(child_evm.value.accounts_to_delete.value.dict_ptr_start, DictAccess*),
cast(child_evm.value.accounts_to_delete.value.dict_ptr, DictAccess*),
cast(accounts_to_delete_end, DictAccess*),
);
tempvar new_accounts_to_delete = SetAddress(
new SetAddressStruct(
dict_ptr_start=cast(accounts_to_delete_start, SetAddressDictAccess*),
dict_ptr=cast(new_accounts_to_delete_end, SetAddressDictAccess*),
),
);

// Squash & update touched_accounts into parent
let child_touched_accounts_start = child_evm.value.touched_accounts.value.dict_ptr_start;
let child_touched_accounts_end = child_evm.value.touched_accounts.value.dict_ptr;
let touched_accounts = evm.value.touched_accounts;
let touched_accounts_end = touched_accounts.value.dict_ptr;
let new_touched_accounts_end = squash_and_update(
cast(child_touched_accounts_start, DictAccess*),
cast(child_touched_accounts_end, DictAccess*),
cast(touched_accounts_end, DictAccess*),
);

// Check if child message target account exists and is empty
let env = evm.value.env;
let state = env.value.state;
let exists_and_is_empty = account_exists_and_is_empty{state=state}(
child_evm.value.message.value.current_target
);
if (exists_and_is_empty.value != 0) {
hashdict_write{dict_ptr=new_touched_accounts_end}(
1, &child_evm.value.message.value.current_target.value, 1
);
} else {
tempvar poseidon_ptr = poseidon_ptr;
tempvar new_touched_accounts_end = new_touched_accounts_end;
}
let poseidon_ptr = cast([ap - 2], PoseidonBuiltin*);
let new_touched_accounts_end = cast([ap - 1], DictAccess*);
EnvImpl.set_state{env=env}(state);

tempvar new_touched_accounts = SetAddress(
new SetAddressStruct(
dict_ptr_start=cast(touched_accounts.value.dict_ptr_start, SetAddressDictAccess*),
dict_ptr=cast(new_touched_accounts_end, SetAddressDictAccess*),
),
);

// Squash & update accessed_addresses into parent
let accessed_addresses = evm.value.accessed_addresses;
let accessed_addresses_start = accessed_addresses.value.dict_ptr_start;
let accessed_addresses_end = accessed_addresses.value.dict_ptr;
let new_accessed_addresses_end = squash_and_update(
cast(child_evm.value.accessed_addresses.value.dict_ptr_start, DictAccess*),
cast(child_evm.value.accessed_addresses.value.dict_ptr, DictAccess*),
cast(accessed_addresses_end, DictAccess*),
);

tempvar new_accessed_addresses = SetAddress(
new SetAddressStruct(
dict_ptr_start=cast(accessed_addresses_start, SetAddressDictAccess*),
dict_ptr=cast(new_accessed_addresses_end, SetAddressDictAccess*),
),
);

// Squash & update accessed_storage_keys into parent
let accessed_storage_keys = evm.value.accessed_storage_keys;
let accessed_storage_keys_start = accessed_storage_keys.value.dict_ptr_start;
let accessed_storage_keys_end = accessed_storage_keys.value.dict_ptr;
let new_accessed_storage_keys_end = squash_and_update(
cast(child_evm.value.accessed_storage_keys.value.dict_ptr_start, DictAccess*),
cast(child_evm.value.accessed_storage_keys.value.dict_ptr, DictAccess*),
cast(accessed_storage_keys_end, DictAccess*),
);

tempvar new_accessed_storage_keys = SetTupleAddressBytes32(
new SetTupleAddressBytes32Struct(
dict_ptr_start=cast(accessed_storage_keys_start, SetTupleAddressBytes32DictAccess*),
dict_ptr=cast(new_accessed_storage_keys_end, SetTupleAddressBytes32DictAccess*),
),
);

// Squash dropped dicts
dict_squash(
cast(child_evm.value.stack.value.dict_ptr_start, DictAccess*),
cast(child_evm.value.stack.value.dict_ptr, DictAccess*),
);

dict_squash(
cast(child_evm.value.memory.value.dict_ptr_start, DictAccess*),
cast(child_evm.value.memory.value.dict_ptr, DictAccess*),
);

dict_squash(
cast(child_evm.value.valid_jump_destinations.value.dict_ptr_start, DictAccess*),
cast(child_evm.value.valid_jump_destinations.value.dict_ptr, DictAccess*),
);

tempvar evm = Evm(
new EvmStruct(
pc=evm.value.pc,
stack=evm.value.stack,
memory=evm.value.memory,
code=evm.value.code,
gas_left=new_gas_left,
env=env,
valid_jump_destinations=evm.value.valid_jump_destinations,
logs=new_logs,
refund_counter=new_refund_counter,
running=evm.value.running,
message=evm.value.message,
output=evm.value.output,
accounts_to_delete=new_accounts_to_delete,
touched_accounts=new_touched_accounts,
return_data=evm.value.return_data,
error=evm.value.error,
accessed_addresses=new_accessed_addresses,
accessed_storage_keys=new_accessed_storage_keys,
),
);

return ();
}

// @notice Incorporates the child EVM in its parent in case of an error.
// @dev This function is responsible for correctly squashing all the child's dicts that are dropped.
func incorporate_child_on_error{range_check_ptr, poseidon_ptr: PoseidonBuiltin*, evm: Evm}(
child_evm: Evm
) {
alloc_locals;

let fp_and_pc = get_fp_and_pc();
local __fp__: felt* = fp_and_pc.fp_val;

// Special handling for RIPEMD160 precompile address (0x3)
// TODO: Move this to precompiled_contracts.cairo
const RIPEMD160_ADDRESS = 0x0300000000000000000000000000000000000000;

// Check if RIPEMD160 address is in child's touched accounts
let child_touched_accounts = child_evm.value.touched_accounts;
let child_touched_accounts_start = child_touched_accounts.value.dict_ptr_start;
let child_touched_accounts_end = cast(child_touched_accounts.value.dict_ptr, DictAccess*);
let (ripemd_touched) = hashdict_read{dict_ptr=child_touched_accounts_end}(
1, new RIPEMD160_ADDRESS
);

// Soundness requirement: squash all child dicts - including ones from message and env.

// EVM //
dict_squash(
cast(child_touched_accounts_start, DictAccess*),
cast(child_touched_accounts_end, DictAccess*),
);

let child_accessed_addresses = child_evm.value.accessed_addresses;
let child_accessed_addresses_start = child_accessed_addresses.value.dict_ptr_start;
let child_accessed_addresses_end = cast(child_accessed_addresses.value.dict_ptr, DictAccess*);
dict_squash(
cast(child_accessed_addresses_start, DictAccess*),
cast(child_accessed_addresses_end, DictAccess*),
);

let child_accessed_storage_keys = child_evm.value.accessed_storage_keys;
let child_accessed_storage_keys_start = child_accessed_storage_keys.value.dict_ptr_start;
let child_accessed_storage_keys_end = cast(
child_accessed_storage_keys.value.dict_ptr, DictAccess*
);
dict_squash(
cast(child_accessed_storage_keys_start, DictAccess*),
cast(child_accessed_storage_keys_end, DictAccess*),
);

let accounts_to_delete = child_evm.value.accounts_to_delete;
let accounts_to_delete_start = accounts_to_delete.value.dict_ptr_start;
let accounts_to_delete_end = cast(accounts_to_delete.value.dict_ptr, DictAccess*);
dict_squash(
cast(accounts_to_delete_start, DictAccess*), cast(accounts_to_delete_end, DictAccess*)
);

let valid_jump_destinations = child_evm.value.valid_jump_destinations;
let valid_jump_destinations_start = valid_jump_destinations.value.dict_ptr_start;
let valid_jump_destinations_end = cast(valid_jump_destinations.value.dict_ptr, DictAccess*);
dict_squash(
cast(valid_jump_destinations_start, DictAccess*),
cast(valid_jump_destinations_end, DictAccess*),
);

let stack = child_evm.value.stack;
let stack_start = stack.value.dict_ptr_start;
let stack_end = cast(stack.value.dict_ptr, DictAccess*);
dict_squash(cast(stack_start, DictAccess*), cast(stack_end, DictAccess*));

let memory = child_evm.value.memory;
let memory_start = memory.value.dict_ptr_start;
let memory_end = cast(memory.value.dict_ptr, DictAccess*);
dict_squash(cast(memory_start, DictAccess*), cast(memory_end, DictAccess*));

// No need to squash the message's `accessed_addresses` and `accessed_storage_keys` because
// they were moved into the Evm, which we just squashed.

// No need to squash the env's `state` and `transient_storage` because it was handled in `rollback_transaction`,
// when we squashed and appended the prev keys to the parent's state and transient storage segments.

// Check if child message target is RIPEMD160 address
let is_ripemd_target = is_zero(
child_evm.value.message.value.current_target.value - RIPEMD160_ADDRESS
);
if (is_ripemd_target != 0) {
let env = evm.value.env;
let state = env.value.state;
let exists_and_is_empty = account_exists_and_is_empty{state=state}(
child_evm.value.message.value.current_target
);
EnvImpl.set_state{env=env}(state);
EvmImpl.set_env(env);
tempvar evm = evm;
tempvar poseidon_ptr = poseidon_ptr;
tempvar write_ripemd = exists_and_is_empty.value + ripemd_touched;
} else {
tempvar evm = evm;
tempvar poseidon_ptr = poseidon_ptr;
tempvar write_ripemd = ripemd_touched;
}
let evm_ = cast([ap - 3], EvmStruct*);
let poseidon_ptr = cast([ap - 2], PoseidonBuiltin*);
let write_ripemd = [ap - 1];
tempvar evm = Evm(evm_);

let touched_accounts = evm.value.touched_accounts;
let touched_accounts_end = cast(touched_accounts.value.dict_ptr, DictAccess*);

if (write_ripemd != 0) {
hashdict_write{dict_ptr=touched_accounts_end}(1, new RIPEMD160_ADDRESS, 1);
} else {
tempvar poseidon_ptr = poseidon_ptr;
tempvar touched_accounts_end = touched_accounts_end;
}
let poseidon_ptr = cast([ap - 2], PoseidonBuiltin*);
let new_touched_accounts_end = cast([ap - 1], DictAccess*);

tempvar new_touched_accounts = SetAddress(
new SetAddressStruct(
dict_ptr_start=cast(touched_accounts.value.dict_ptr_start, SetAddressDictAccess*),
dict_ptr=cast(new_touched_accounts_end, SetAddressDictAccess*),
),
);
let new_gas_left = Uint(evm.value.gas_left.value + child_evm.value.gas_left.value);

tempvar evm = Evm(
new EvmStruct(
pc=evm.value.pc,
stack=evm.value.stack,
memory=evm.value.memory,
code=evm.value.code,
gas_left=new_gas_left,
env=evm.value.env,
valid_jump_destinations=evm.value.valid_jump_destinations,
logs=evm.value.logs,
refund_counter=evm.value.refund_counter,
running=evm.value.running,
message=evm.value.message,
output=evm.value.output,
accounts_to_delete=evm.value.accounts_to_delete,
touched_accounts=new_touched_accounts,
return_data=evm.value.return_data,
error=evm.value.error,
accessed_addresses=evm.value.accessed_addresses,
accessed_storage_keys=evm.value.accessed_storage_keys,
),
);

return ();
}

namespace EvmImpl {
func set_pc{evm: Evm}(new_pc: Uint) {
tempvar evm = Evm(
Expand Down
Loading

0 comments on commit 553510e

Please sign in to comment.