zksync_vm2/instruction_handlers/
far_call.rs1use primitive_types::U256;
2use zkevm_opcode_defs::{system_params::MSG_VALUE_SIMULATOR_ADDITIVE_COST, ADDRESS_MSG_VALUE};
3use zksync_vm2_interface::{
4 opcodes::{FarCall, TypeLevelCallingMode},
5 Tracer,
6};
7
8use super::{
9 common::full_boilerplate,
10 heap_access::grow_heap,
11 monomorphization::{match_boolean, monomorphize, parameterize},
12 AuxHeap, Heap,
13};
14use crate::{
15 addressing_modes::{Arguments, Immediate1, Register1, Register2, Source},
16 decommit::{is_kernel, materialize_decommit_page, u256_into_address},
17 fat_pointer::FatPointer,
18 instruction::ExecutionStatus,
19 page_ids::code_page_from_base,
20 predication::Flags,
21 Instruction, Program, VirtualMachine, World,
22};
23
24fn far_call<T, W, M, const IS_STATIC: bool, const IS_SHARD: bool>(
35 vm: &mut VirtualMachine<T, W>,
36 world: &mut W,
37 tracer: &mut T,
38) -> ExecutionStatus
39where
40 T: Tracer,
41 W: World<T>,
42 M: TypeLevelCallingMode,
43{
44 full_boilerplate::<FarCall<M>, _, _>(vm, world, tracer, |vm, args, world, tracer| {
45 let (raw_abi, raw_abi_is_pointer) = Register1::get_with_pointer_flag(args, &mut vm.state);
46
47 let address_mask: U256 = U256::MAX >> (256 - 160);
48 let destination_address = Register2::get(args, &mut vm.state) & address_mask;
49 let exception_handler = Immediate1::get_u16(args);
50
51 let mut abi = get_far_call_arguments(raw_abi);
52 abi.is_constructor_call = abi.is_constructor_call && vm.state.current_frame.is_kernel;
53 abi.is_system_call =
54 abi.is_system_call && is_kernel(u256_into_address(destination_address));
55
56 let mut mandated_gas =
57 if abi.is_system_call && destination_address == ADDRESS_MSG_VALUE.into() {
58 MSG_VALUE_SIMULATOR_ADDITIVE_COST
59 } else {
60 0
61 };
62 let new_base_page = vm.state.next_base_page();
63
64 let fallible_part = (|| {
65 let shard_call_failed = IS_SHARD && abi.shard_id != 0;
66
67 let (maybe_calldata, decommit_result) = if shard_call_failed {
68 (get_calldata(raw_abi, raw_abi_is_pointer, vm, true), None)
71 } else {
72 let decommit_result = vm.world_diff.decommit(
73 world,
74 tracer,
75 destination_address,
76 vm.settings.default_aa_code_hash,
77 vm.settings.evm_interpreter_code_hash,
78 abi.is_constructor_call,
79 vm.state.transaction_number,
80 );
81
82 let already_failed = decommit_result.is_none();
85 let maybe_calldata = get_calldata(raw_abi, raw_abi_is_pointer, vm, already_failed);
86 (maybe_calldata, decommit_result)
87 };
88
89 if let Some(gas_left) = vm.state.current_frame.gas.checked_sub(mandated_gas) {
91 vm.state.current_frame.gas = gas_left;
92 } else {
93 vm.state.current_frame.gas = 0;
95 mandated_gas = 0;
96 return None;
97 }
98
99 if shard_call_failed {
100 return None;
101 }
102 let calldata = maybe_calldata?;
103 let (unpaid_decommit, is_evm, is_evm_blob_format) = decommit_result?;
104 let code_hash = unpaid_decommit.code_key();
105 let should_materialize = unpaid_decommit.should_materialize();
106 let program = vm.world_diff.pay_for_decommit(
107 world,
108 tracer,
109 unpaid_decommit,
110 &mut vm.state.current_frame.gas,
111 )?;
112
113 if should_materialize {
114 let code = program_to_bytes(&program);
119 materialize_decommit_page(vm, code_hash, &code, code_page_from_base(new_base_page));
120 }
121
122 Some((calldata, program, is_evm, is_evm_blob_format))
123 })();
124
125 let maximum_gas = vm.state.current_frame.gas / 64 * 63;
126 let normally_passed_gas = abi.gas_to_pass.min(maximum_gas);
127 vm.state.current_frame.gas -= normally_passed_gas;
128 let new_frame_gas = normally_passed_gas + mandated_gas;
129
130 let (calldata, program, is_evm_interpreter, is_evm_blob_format) = fallible_part
132 .unwrap_or_else(|| (U256::zero().into(), Program::new_panicking(), false, false));
133
134 let new_frame_is_static = IS_STATIC || vm.state.current_frame.is_static;
135 vm.push_frame::<M>(
136 u256_into_address(destination_address),
137 program,
138 new_frame_gas,
139 exception_handler,
140 new_frame_is_static && !is_evm_interpreter,
141 is_evm_blob_format,
142 calldata.memory_page,
143 vm.world_diff.snapshot(),
144 );
145
146 vm.state.flags = Flags::new(false, false, false);
147
148 if abi.is_system_call {
149 vm.state.registers[13] = U256::zero();
151 vm.state.registers[14] = U256::zero();
152 vm.state.registers[15] = U256::zero();
153 } else {
154 vm.state.registers = [U256::zero(); 16];
155 }
156
157 vm.state.register_pointer_flags = 2;
159 vm.state.registers[1] = calldata.into_u256();
160
161 let is_static_call_to_evm_interpreter = new_frame_is_static && is_evm_interpreter;
162 let call_type = (u8::from(is_static_call_to_evm_interpreter) << 2)
163 | (u8::from(abi.is_system_call) << 1)
164 | u8::from(abi.is_constructor_call);
165
166 vm.state.registers[2] = call_type.into();
167
168 ExecutionStatus::Running
169 })
170}
171
172#[derive(Debug)]
173pub(crate) struct FarCallABI {
174 pub(crate) gas_to_pass: u32,
175 pub(crate) shard_id: u8,
176 pub(crate) is_constructor_call: bool,
177 pub(crate) is_system_call: bool,
178}
179
180#[allow(clippy::cast_possible_truncation)] fn get_far_call_arguments(abi: U256) -> FarCallABI {
182 let gas_to_pass = abi.0[3] as u32;
183 let settings = (abi.0[3] >> 32) as u32;
184 let [_, shard_id, constructor_call_byte, system_call_byte] = settings.to_le_bytes();
185
186 FarCallABI {
187 gas_to_pass,
188 shard_id,
189 is_constructor_call: constructor_call_byte != 0,
190 is_system_call: system_call_byte != 0,
191 }
192}
193
194fn program_to_bytes<T, W>(program: &Program<T, W>) -> Vec<u8> {
195 let mut result = Vec::with_capacity(program.code_page().len() * 32);
196 #[allow(clippy::explicit_iter_loop)] for word in program.code_page().iter() {
198 let mut bytes = [0u8; 32];
199 word.to_big_endian(&mut bytes);
200 result.extend_from_slice(&bytes);
201 }
202 result
203}
204
205pub(crate) fn get_calldata<T, W>(
210 raw_abi: U256,
211 is_pointer: bool,
212 vm: &mut VirtualMachine<T, W>,
213 already_failed: bool,
214) -> Option<FatPointer> {
215 let mut pointer = FatPointer::from(raw_abi);
216 #[allow(clippy::cast_possible_truncation)]
217 let raw_source = (raw_abi.0[3] >> 32) as u8;
219
220 match FatPointerSource::from_abi(raw_source) {
221 FatPointerSource::ForwardFatPointer => {
222 if !is_pointer || pointer.offset > pointer.length {
223 return None;
224 }
225
226 pointer.narrow();
227 }
228 FatPointerSource::MakeNewPointer(target) => {
229 let mut grow = |size| {
230 match target {
231 FatPointerTarget::ToHeap => {
232 grow_heap::<_, _, Heap>(&mut vm.state, size).ok()?;
233 pointer.memory_page = vm.state.current_frame.heap;
234 }
235 FatPointerTarget::ToAuxHeap => {
236 grow_heap::<_, _, AuxHeap>(&mut vm.state, size).ok()?;
237 pointer.memory_page = vm.state.current_frame.aux_heap;
238 }
239 }
240 Some(())
241 };
242
243 if let Some(bound) = pointer.start.checked_add(pointer.length) {
246 if is_pointer || pointer.offset != 0 || already_failed {
247 return None;
248 }
249 grow(bound)?;
250 } else {
251 grow(u32::MAX);
252 return None;
253 }
254 }
255 }
256
257 Some(pointer)
258}
259
260#[derive(Debug)]
261enum FatPointerSource {
262 MakeNewPointer(FatPointerTarget),
263 ForwardFatPointer,
264}
265
266#[derive(Debug)]
267enum FatPointerTarget {
268 ToHeap,
269 ToAuxHeap,
270}
271
272impl FatPointerSource {
273 const fn from_abi(value: u8) -> Self {
274 match value {
275 1 => Self::ForwardFatPointer,
276 2 => Self::MakeNewPointer(FatPointerTarget::ToAuxHeap),
277 _ => Self::MakeNewPointer(FatPointerTarget::ToHeap), }
279 }
280}
281
282impl FatPointer {
283 fn narrow(&mut self) {
284 self.start += self.offset;
285 self.length -= self.offset;
286 self.offset = 0;
287 }
288}
289
290impl<T: Tracer, W: World<T>> Instruction<T, W> {
291 pub fn from_far_call<M: TypeLevelCallingMode>(
293 src1: Register1,
294 src2: Register2,
295 error_handler: Immediate1,
296 is_static: bool,
297 is_shard: bool,
298 arguments: Arguments,
299 ) -> Self {
300 Self {
301 handler: monomorphize!(far_call [T W M] match_boolean is_static match_boolean is_shard),
302 arguments: arguments
303 .write_source(&src1)
304 .write_source(&src2)
305 .write_source(&error_handler),
306 }
307 }
308}