use super::*;
use crate::zkevm_opcode_defs::definitions::far_call::*;
use crate::zkevm_opcode_defs::INITIAL_SP_ON_FAR_CALL;
use zk_evm_abstractions::aux::*;
use zk_evm_abstractions::queries::LogQuery;
use zk_evm_abstractions::zkevm_opcode_defs::{BlobSha256Format, VersionedHashLen32};
use crate::zkevm_opcode_defs::bitflags::bitflags;
pub const FORCED_ERGS_FOR_MSG_VALUE_SIMULATOR: bool = false;
bitflags! {
pub struct FarCallExceptionFlags: u64 {
const INPUT_IS_NOT_POINTER_WHEN_EXPECTED = 1u64 << 0;
const INVALID_CODE_HASH_FORMAT = 1u64 << 1;
const NOT_ENOUGH_ERGS_TO_DECOMMIT = 1u64 << 2;
const NOT_ENOUGH_ERGS_TO_GROW_MEMORY = 1u64 << 3;
const MALFORMED_ABI_QUASI_POINTER = 1u64 << 4;
const CALL_IN_NOW_CONSTRUCTED_SYSTEM_CONTRACT = 1u64 << 5;
const NOT_ENOUGH_ERGS_FOR_EXTRA_FAR_CALL_COSTS = 1u64 << 6;
const CALL_TO_UNREACHABLE_ADDRESS = 1u64 << 7;
const INPUT_IS_POINTER_WHEN_NOT_EXPECTED = 1u64 << 8;
}
}
use crate::zkevm_opcode_defs::{FarCallABI, FarCallOpcode, Opcode};
impl<const N: usize, E: VmEncodingMode<N>> DecodedOpcode<N, E> {
pub fn far_call_opcode_apply<
S: zk_evm_abstractions::vm::Storage,
M: zk_evm_abstractions::vm::Memory,
EV: zk_evm_abstractions::vm::EventSink,
PP: zk_evm_abstractions::vm::PrecompilesProcessor,
DP: zk_evm_abstractions::vm::DecommittmentProcessor,
WT: crate::witness_trace::VmWitnessTracer<N, E>,
>(
&self,
vm_state: &mut VmState<S, M, EV, PP, DP, WT, N, E>,
prestate: PreState<N, E>,
) -> anyhow::Result<()> {
let PreState {
src0,
src1,
new_pc,
is_kernel_mode,
..
} = prestate;
let inner_variant = match self.variant.opcode {
Opcode::FarCall(inner) => inner,
_ => unreachable!(),
};
let PrimitiveValue {
value: abi_src,
is_pointer: abi_src_is_ptr,
} = src0;
let PrimitiveValue {
value: call_destination_value,
is_pointer: _,
} = src1;
vm_state.reset_flags();
let is_static_call = self.variant.flags[FAR_CALL_STATIC_FLAG_IDX];
let is_call_shard = self.variant.flags[FAR_CALL_SHARD_FLAG_IDX];
let exception_handler_location = self.imm_0;
let called_address = u256_to_address_unchecked(&call_destination_value);
let called_address_as_u256 = call_destination_value & *U256_TO_ADDRESS_MASK;
let dst_is_kernel = address_is_kernel(&called_address);
let mut far_call_abi = FarCallABI::from_u256(abi_src);
far_call_abi.ergs_passed = if let Some(non_overflowing) =
far_call_abi.ergs_passed.checked_mul(
zkevm_opcode_defs::system_params::INTERNAL_ERGS_TO_VISIBLE_ERGS_CONVERSION_CONSTANT,
) {
non_overflowing
} else {
u32::MAX
};
far_call_abi.constructor_call = far_call_abi.constructor_call & is_kernel_mode;
far_call_abi.to_system = far_call_abi.to_system & dst_is_kernel;
let current_stack = vm_state.local_state.callstack.get_current_stack();
let current_address = current_stack.this_address;
let current_msg_sender = current_stack.msg_sender;
let current_base_page = current_stack.base_memory_page;
let caller_shard_id = current_stack.this_shard_id;
let remaining_ergs = current_stack.ergs_remaining;
let current_context_u128 = current_stack.context_u128_value;
#[allow(dropping_references)]
drop(current_stack);
let timestamp_for_storage_read = vm_state.timestamp_for_first_decommit_or_precompile_read();
let tx_number_in_block = vm_state.local_state.tx_number_in_block;
let new_code_shard_id = if is_call_shard {
far_call_abi.shard_id
} else {
caller_shard_id
};
let new_this_shard_id = if inner_variant == FarCallOpcode::Delegate {
caller_shard_id
} else {
new_code_shard_id
};
let new_base_memory_page = vm_state.new_base_memory_page_on_call();
let call_to_evm_emulator;
let code_version_byte;
let (
mapped_code_page,
ergs_after_code_read_and_exceptions_resolution,
extra_ergs_from_caller_to_callee,
callee_stipend,
) = {
let (code_hash, call_to_unreachable) = if new_code_shard_id != 0
&& !vm_state.block_properties.zkporter_is_available
{
(U256::zero(), true)
} else {
let partial_query = LogQuery {
timestamp: timestamp_for_storage_read,
tx_number_in_block,
aux_byte: STORAGE_AUX_BYTE,
shard_id: new_code_shard_id,
address: *DEPLOYER_SYSTEM_CONTRACT_ADDRESS,
key: called_address_as_u256,
read_value: U256::zero(),
written_value: U256::zero(),
rw_flag: false,
rollback: false,
is_service: false,
};
let (query, _) = vm_state
.access_storage(vm_state.local_state.monotonic_cycle_counter, partial_query);
let code_hash_from_storage = query.read_value;
(code_hash_from_storage, false)
};
let mut exceptions = FarCallExceptionFlags::empty();
if call_to_unreachable == true {
exceptions.set(FarCallExceptionFlags::CALL_TO_UNREACHABLE_ADDRESS, true);
}
use crate::zkevm_opcode_defs::*;
let bytecode_hash_is_empty = code_hash == U256::zero();
let mut buffer = [0u8; 32];
code_hash.to_big_endian(&mut buffer);
code_version_byte = buffer[0];
let is_valid_as_bytecode_hash = ContractCodeSha256Format::is_valid(&buffer);
let is_valid_as_blob_hash = BlobSha256Format::is_valid(&buffer);
let mut mask_to_default_aa = false;
let can_call_code_without_masking = if is_valid_as_bytecode_hash {
let is_code_at_rest = ContractCodeSha256Format::is_code_at_rest_if_valid(&buffer);
let is_constructed = ContractCodeSha256Format::is_in_construction_if_valid(&buffer);
let can_call_at_rest = !far_call_abi.constructor_call && is_code_at_rest;
let can_call_by_constructor = far_call_abi.constructor_call && is_constructed;
let can_call_code_without_masking = can_call_at_rest || can_call_by_constructor;
if can_call_code_without_masking == true {
true
} else {
if dst_is_kernel == false {
mask_to_default_aa = true;
} else {
exceptions.set(
FarCallExceptionFlags::CALL_IN_NOW_CONSTRUCTED_SYSTEM_CONTRACT,
true,
);
}
false
}
} else {
false
};
let can_call_evm_emulator = if is_valid_as_blob_hash {
let is_code_at_rest = BlobSha256Format::is_code_at_rest_if_valid(&buffer);
let is_constructed = BlobSha256Format::is_in_construction_if_valid(&buffer);
let can_call_at_rest = !far_call_abi.constructor_call && is_code_at_rest;
let can_call_by_constructor = far_call_abi.constructor_call && is_constructed;
let can_call_code_without_masking = can_call_at_rest || can_call_by_constructor;
if can_call_code_without_masking == true {
true
} else {
if dst_is_kernel == false {
mask_to_default_aa = true;
} else {
exceptions.set(FarCallExceptionFlags::INVALID_CODE_HASH_FORMAT, true);
}
false
}
} else {
false
};
call_to_evm_emulator = can_call_evm_emulator;
if bytecode_hash_is_empty {
if dst_is_kernel == false {
mask_to_default_aa = true;
} else {
exceptions.set(FarCallExceptionFlags::INVALID_CODE_HASH_FORMAT, true);
}
}
assert!(
(mask_to_default_aa as u64)
+ (can_call_evm_emulator as u64)
+ (can_call_code_without_masking as u64)
< 2
);
let unknown_hash = mask_to_default_aa == false
&& can_call_evm_emulator == false
&& can_call_code_without_masking == false;
if unknown_hash {
exceptions.set(FarCallExceptionFlags::INVALID_CODE_HASH_FORMAT, true);
}
let (header, normalized_preimage, code_length_in_words) = {
if can_call_code_without_masking {
} else if can_call_evm_emulator {
vm_state
.block_properties
.evm_emulator_code_hash
.to_big_endian(&mut buffer);
} else if mask_to_default_aa {
vm_state
.block_properties
.default_aa_code_hash
.to_big_endian(&mut buffer);
} else {
assert!(exceptions.is_empty() == false);
}
if exceptions.is_empty() {
assert!(
can_call_code_without_masking
|| can_call_evm_emulator
|| mask_to_default_aa
);
let length_in_words =
ContractCodeSha256Format::code_length_in_bytes32_words(&buffer);
let (header, normalized_preimage) =
ContractCodeSha256Format::normalize_for_decommitment(&buffer);
(header, normalized_preimage, length_in_words)
} else {
(
VersionedHashHeader::default(),
VersionedHashNormalizedPreimage::default(),
0u16,
)
}
};
if far_call_abi.forwarding_mode == FarCallForwardPageType::ForwardFatPointer {
if abi_src_is_ptr == false {
exceptions.set(
FarCallExceptionFlags::INPUT_IS_NOT_POINTER_WHEN_EXPECTED,
true,
);
}
} else {
if abi_src_is_ptr {
exceptions.set(
FarCallExceptionFlags::INPUT_IS_POINTER_WHEN_NOT_EXPECTED,
true,
);
}
}
let validate_as_fresh =
far_call_abi.forwarding_mode != FarCallForwardPageType::ForwardFatPointer;
let pointer_validation_exceptions = far_call_abi
.memory_quasi_fat_pointer
.validate(validate_as_fresh);
if pointer_validation_exceptions.is_empty() == false {
exceptions.set(FarCallExceptionFlags::MALFORMED_ABI_QUASI_POINTER, true);
}
if far_call_abi.memory_quasi_fat_pointer.validate_as_slice() == false {
exceptions.set(FarCallExceptionFlags::MALFORMED_ABI_QUASI_POINTER, true);
}
match far_call_abi.forwarding_mode {
FarCallForwardPageType::ForwardFatPointer => {
let new_start = far_call_abi
.memory_quasi_fat_pointer
.start
.wrapping_add(far_call_abi.memory_quasi_fat_pointer.offset);
let new_length = far_call_abi
.memory_quasi_fat_pointer
.length
.wrapping_sub(far_call_abi.memory_quasi_fat_pointer.offset);
far_call_abi.memory_quasi_fat_pointer.start = new_start;
far_call_abi.memory_quasi_fat_pointer.length = new_length;
far_call_abi.memory_quasi_fat_pointer.offset = 0;
}
FarCallForwardPageType::UseHeap => {
let owned_page = heap_page_from_base(current_base_page).0;
far_call_abi.memory_quasi_fat_pointer.memory_page = owned_page;
}
FarCallForwardPageType::UseAuxHeap => {
let owned_page = aux_heap_page_from_base(current_base_page).0;
far_call_abi.memory_quasi_fat_pointer.memory_page = owned_page;
}
};
if exceptions.is_empty() == false {
far_call_abi.memory_quasi_fat_pointer = FatPointer::empty();
}
let current_stack_mut = vm_state.local_state.callstack.get_current_stack_mut();
let remaining_ergs_after_growth = if vm_state.version >= Version::Version27 {
match far_call_abi.forwarding_mode {
a @ FarCallForwardPageType::UseHeap
| a @ FarCallForwardPageType::UseAuxHeap => {
let upper_bound = if pointer_validation_exceptions
.contains(FatPointerValidationException::DEREF_BEYOND_HEAP_RANGE)
{
u32::MAX
} else {
far_call_abi.memory_quasi_fat_pointer.start
+ far_call_abi.memory_quasi_fat_pointer.length
};
let current_bound = if a == FarCallForwardPageType::UseHeap {
current_stack_mut.heap_bound
} else if a == FarCallForwardPageType::UseAuxHeap {
current_stack_mut.aux_heap_bound
} else {
unreachable!();
};
let growth_cost = upper_bound.saturating_sub(current_bound);
if remaining_ergs >= growth_cost {
if upper_bound > current_bound {
if a == FarCallForwardPageType::UseHeap {
current_stack_mut.heap_bound = upper_bound;
} else if a == FarCallForwardPageType::UseAuxHeap {
current_stack_mut.aux_heap_bound = upper_bound;
} else {
unreachable!();
}
}
remaining_ergs - growth_cost
} else {
exceptions
.set(FarCallExceptionFlags::NOT_ENOUGH_ERGS_TO_GROW_MEMORY, true);
0
}
}
FarCallForwardPageType::ForwardFatPointer => remaining_ergs,
}
} else {
let memory_growth_in_bytes = match far_call_abi.forwarding_mode {
a @ FarCallForwardPageType::UseHeap
| a @ FarCallForwardPageType::UseAuxHeap => {
let mut upper_bound = far_call_abi.memory_quasi_fat_pointer.start
+ far_call_abi.memory_quasi_fat_pointer.length;
let penalize_out_of_bounds_growth = pointer_validation_exceptions
.contains(FatPointerValidationException::DEREF_BEYOND_HEAP_RANGE);
if penalize_out_of_bounds_growth {
upper_bound = u32::MAX;
}
let current_bound = if a == FarCallForwardPageType::UseHeap {
current_stack_mut.heap_bound
} else if a == FarCallForwardPageType::UseAuxHeap {
current_stack_mut.aux_heap_bound
} else {
unreachable!();
};
let (mut diff, uf) = upper_bound.overflowing_sub(current_bound);
if uf {
diff = 0u32;
} else {
if a == FarCallForwardPageType::UseHeap {
current_stack_mut.heap_bound = upper_bound;
} else if a == FarCallForwardPageType::UseAuxHeap {
current_stack_mut.aux_heap_bound = upper_bound;
} else {
unreachable!();
}
}
diff
}
FarCallForwardPageType::ForwardFatPointer => 0u32,
};
let cost_of_memory_growth = memory_growth_in_bytes
.wrapping_mul(zkevm_opcode_defs::MEMORY_GROWTH_ERGS_PER_BYTE);
if remaining_ergs >= cost_of_memory_growth {
remaining_ergs - cost_of_memory_growth
} else {
exceptions.set(FarCallExceptionFlags::NOT_ENOUGH_ERGS_TO_GROW_MEMORY, true);
0
}
};
let (callee_stipend, mut extra_ergs_from_caller_to_callee) =
get_stipend_and_extra_cost(&called_address, far_call_abi.to_system);
let remaining_ergs_of_caller_frame =
if remaining_ergs_after_growth >= extra_ergs_from_caller_to_callee {
remaining_ergs_after_growth - extra_ergs_from_caller_to_callee
} else {
exceptions.set(
FarCallExceptionFlags::NOT_ENOUGH_ERGS_FOR_EXTRA_FAR_CALL_COSTS,
true,
);
extra_ergs_from_caller_to_callee = 0;
0
};
let (code_memory_page, remaining_ergs_after_decommittment) = if exceptions.is_empty()
== false
{
vm_state.set_shorthand_panic();
(MemoryPage(UNMAPPED_PAGE), remaining_ergs_of_caller_frame)
} else {
assert!(
header.0 != [0u8; 4],
"zero header must be masked to default aa or panic",
);
let default_cost_of_decommittment =
zkevm_opcode_defs::ERGS_PER_CODE_WORD_DECOMMITTMENT
* (code_length_in_words as u32);
let timestamp_for_decommit =
vm_state.timestamp_for_first_decommit_or_precompile_read();
let memory_page_candidate_for_code_decommittment =
code_page_candidate_from_base(new_base_memory_page);
let prepared_decommmit_query = vm_state.prepare_to_decommit(
vm_state.local_state.monotonic_cycle_counter,
header,
normalized_preimage,
memory_page_candidate_for_code_decommittment,
timestamp_for_decommit,
)?;
let cost_of_decommittment = if prepared_decommmit_query.is_fresh {
default_cost_of_decommittment
} else {
0
};
let remaining_ergs_after_decommittment =
if remaining_ergs_of_caller_frame >= cost_of_decommittment {
remaining_ergs_of_caller_frame - cost_of_decommittment
} else {
exceptions.set(FarCallExceptionFlags::NOT_ENOUGH_ERGS_TO_DECOMMIT, true);
remaining_ergs_of_caller_frame };
let memory_page = if exceptions.is_empty() {
vm_state.execute_decommit(
vm_state.local_state.monotonic_cycle_counter,
prepared_decommmit_query,
)?;
prepared_decommmit_query.memory_page
} else {
vm_state.set_shorthand_panic();
MemoryPage(UNMAPPED_PAGE)
};
(memory_page, remaining_ergs_after_decommittment)
};
(
code_memory_page,
remaining_ergs_after_decommittment,
extra_ergs_from_caller_to_callee,
callee_stipend,
)
};
let remaining_ergs_to_pass = ergs_after_code_read_and_exceptions_resolution;
let max_passable = (remaining_ergs_to_pass / 64) * 63; let leftover = remaining_ergs_to_pass - max_passable;
let (passed_ergs, remaining_ergs_for_this_context) = {
let (remaining_from_max_passable, uf) =
max_passable.overflowing_sub(far_call_abi.ergs_passed);
if uf {
(max_passable, leftover)
} else {
(
far_call_abi.ergs_passed,
leftover + remaining_from_max_passable,
)
}
};
let passed_ergs = passed_ergs.wrapping_add(extra_ergs_from_caller_to_callee);
let passed_ergs = passed_ergs
.checked_add(callee_stipend)
.expect("stipends must never overflow");
vm_state
.local_state
.callstack
.get_current_stack_mut()
.ergs_remaining = remaining_ergs_for_this_context;
vm_state.local_state.callstack.get_current_stack_mut().pc = new_pc;
let current_stack = vm_state.local_state.callstack.get_current_stack();
let new_context_is_static = current_stack.is_static | is_static_call;
vm_state.increment_memory_pages_on_call();
let implicit_reg =
&vm_state.local_state.registers[CALL_IMPLICIT_PARAMETER_REG_IDX as usize];
let address_from_implicit_reg = u256_to_address_unchecked(&implicit_reg.value);
let (address_for_next, msg_sender_for_next) = match inner_variant {
FarCallOpcode::Normal => {
(called_address, current_address)
}
FarCallOpcode::Delegate => {
(current_address, current_msg_sender)
}
FarCallOpcode::Mimic => {
(called_address, address_from_implicit_reg)
}
};
let code_address_for_next = called_address;
let context_u128_for_next = match inner_variant {
FarCallOpcode::Normal | FarCallOpcode::Mimic => {
vm_state.local_state.context_u128_register
}
FarCallOpcode::Delegate => {
current_context_u128
}
};
let is_static_to_set = if call_to_evm_emulator {
false
} else {
new_context_is_static
};
let memory_stipend_userspace = if code_version_byte == BlobSha256Format::VERSION_BYTE {
zkevm_opcode_defs::system_params::NEW_EVM_FRAME_MEMORY_STIPEND
} else {
zkevm_opcode_defs::system_params::NEW_FRAME_MEMORY_STIPEND
};
let memory_stipend = if address_is_kernel(&address_for_next) {
zkevm_opcode_defs::system_params::NEW_KERNEL_FRAME_MEMORY_STIPEND
} else {
memory_stipend_userspace
};
let new_stack = CallStackEntry {
this_address: address_for_next,
msg_sender: msg_sender_for_next,
code_address: code_address_for_next,
base_memory_page: new_base_memory_page,
code_page: mapped_code_page,
sp: E::PcOrImm::from_u64_clipped(INITIAL_SP_ON_FAR_CALL),
pc: E::PcOrImm::from_u64_clipped(0u64),
exception_handler_location,
ergs_remaining: passed_ergs,
this_shard_id: new_this_shard_id,
caller_shard_id,
code_shard_id: new_code_shard_id,
is_static: is_static_to_set,
is_local_frame: false,
context_u128_value: context_u128_for_next,
heap_bound: memory_stipend,
aux_heap_bound: memory_stipend,
total_pubdata_spent: PubdataCost(0i32),
stipend: callee_stipend,
};
vm_state.local_state.context_u128_register = 0;
vm_state.start_frame(vm_state.local_state.monotonic_cycle_counter, new_stack);
vm_state.memory.start_global_frame(
current_base_page,
new_base_memory_page,
far_call_abi.memory_quasi_fat_pointer,
Timestamp(vm_state.local_state.timestamp),
);
let r1_value = PrimitiveValue {
value: far_call_abi.memory_quasi_fat_pointer.to_u256(),
is_pointer: true,
};
vm_state.local_state.registers[CALL_IMPLICIT_CALLDATA_FAT_PTR_REGISTER as usize] = r1_value;
let mut r2_value = U256::zero();
if far_call_abi.constructor_call {
r2_value.0[0] |= 1u64;
}
if far_call_abi.to_system {
r2_value.0[0] |= 1u64 << 1;
}
if call_to_evm_emulator {
r2_value.0[0] |= (new_context_is_static as u64) << 2;
}
vm_state.local_state.registers[CALL_IMPLICIT_CONSTRUCTOR_MARKER_REGISTER as usize] =
PrimitiveValue {
value: r2_value,
is_pointer: false,
};
if far_call_abi.to_system == false {
for reg_idx in CALL_SYSTEM_ABI_REGISTERS {
vm_state.local_state.registers[reg_idx as usize] = PrimitiveValue::empty();
}
} else {
for reg_idx in CALL_SYSTEM_ABI_REGISTERS {
vm_state.local_state.registers[reg_idx as usize].is_pointer = false;
}
}
for reg_idx in CALL_RESERVED_RANGE {
vm_state.local_state.registers[reg_idx as usize] = PrimitiveValue::empty();
}
vm_state.local_state.registers[CALL_IMPLICIT_PARAMETER_REG_IDX as usize] =
PrimitiveValue::empty();
Ok(())
}
}