Skip to main content

zksync_vm2/instruction_handlers/
precompiles.rs

1use primitive_types::U256;
2use zksync_vm2_interface::{opcodes, HeapId, Tracer};
3
4use super::common::boilerplate_ext;
5use crate::{
6    addressing_modes::{Arguments, Destination, Register1, Register2, Source},
7    instruction::ExecutionStatus,
8    precompiles::{PrecompileMemoryReader, Precompiles},
9    Instruction, VirtualMachine, World,
10};
11
12#[derive(Debug)]
13struct PrecompileAuxData {
14    extra_ergs_cost: u32,
15    extra_pubdata_cost: u32,
16}
17
18impl PrecompileAuxData {
19    #[allow(clippy::cast_possible_truncation)]
20    fn from_u256(raw_value: U256) -> Self {
21        let raw = raw_value.0;
22        let extra_ergs_cost = raw[0] as u32;
23        let extra_pubdata_cost = (raw[0] >> 32) as u32;
24
25        Self {
26            extra_ergs_cost,
27            extra_pubdata_cost,
28        }
29    }
30}
31
32#[derive(Debug)]
33struct PrecompileCallAbi {
34    input_memory_offset: u32,
35    input_memory_length: u32,
36    output_memory_offset: u32,
37    output_memory_length: u32,
38    memory_page_to_read: HeapId,
39    memory_page_to_write: HeapId,
40    precompile_interpreted_data: u64,
41}
42
43impl PrecompileCallAbi {
44    #[allow(clippy::cast_possible_truncation)]
45    fn from_u256(raw_value: U256) -> Self {
46        let raw = raw_value.0;
47        let input_memory_offset = raw[0] as u32;
48        let input_memory_length = (raw[0] >> 32) as u32;
49        let output_memory_offset = raw[1] as u32;
50        let output_memory_length = (raw[1] >> 32) as u32;
51        let memory_page_to_read = HeapId::from_u32_unchecked(raw[2] as u32);
52        let memory_page_to_write = HeapId::from_u32_unchecked((raw[2] >> 32) as u32);
53        let precompile_interpreted_data = raw[3];
54
55        Self {
56            input_memory_offset,
57            input_memory_length,
58            output_memory_offset,
59            output_memory_length,
60            memory_page_to_read,
61            memory_page_to_write,
62            precompile_interpreted_data,
63        }
64    }
65}
66
67fn precompile_call<T: Tracer, W: World<T>>(
68    vm: &mut VirtualMachine<T, W>,
69    world: &mut W,
70    tracer: &mut T,
71) -> ExecutionStatus {
72    boilerplate_ext::<opcodes::PrecompileCall, _, _>(
73        vm,
74        world,
75        tracer,
76        |vm, args, world, tracer| {
77            // The user gets to decide how much gas to burn
78            // This is safe because system contracts are trusted
79            let aux_data = PrecompileAuxData::from_u256(Register2::get(args, &mut vm.state));
80            let Ok(()) = vm.state.use_gas(aux_data.extra_ergs_cost) else {
81                Register1::set(args, &mut vm.state, U256::zero());
82                return;
83            };
84
85            #[allow(clippy::cast_possible_wrap)]
86            {
87                vm.world_diff.pubdata.0 += aux_data.extra_pubdata_cost as i32;
88            }
89
90            let mut abi = PrecompileCallAbi::from_u256(Register1::get(args, &mut vm.state));
91            // `zk_evm` uses `memory_page == 0` in precompile ABI as a sentinel meaning
92            // "use the current heap", so remap it before heap lookup.
93            if abi.memory_page_to_read.as_u32() == 0 {
94                abi.memory_page_to_read = vm.state.current_frame.heap;
95            }
96            if abi.memory_page_to_write.as_u32() == 0 {
97                abi.memory_page_to_write = vm.state.current_frame.heap;
98            }
99
100            let address_bytes = vm.state.current_frame.address.0;
101            let address_low = u16::from_le_bytes([address_bytes[19], address_bytes[18]]);
102            let heap_to_read = &vm.state.heaps[abi.memory_page_to_read];
103            let memory = PrecompileMemoryReader::new(
104                heap_to_read,
105                abi.input_memory_offset,
106                abi.input_memory_length,
107            );
108            let output = world.precompiles().call_precompile(
109                address_low,
110                memory,
111                abi.precompile_interpreted_data,
112            );
113
114            if let Some(cycle_stats) = output.cycle_stats {
115                tracer.on_extra_prover_cycles(cycle_stats);
116            }
117
118            let mut write_offset = abi.output_memory_offset * 32;
119            for i in 0..output.len.min(abi.output_memory_length) {
120                vm.state.heaps.write_u256(
121                    abi.memory_page_to_write,
122                    write_offset,
123                    output.buffer[i as usize],
124                );
125                write_offset += 32;
126            }
127            Register1::set(args, &mut vm.state, 1.into());
128        },
129    )
130}
131
132impl<T: Tracer, W: World<T>> Instruction<T, W> {
133    /// Creates a [`PrecompileCall`](opcodes::PrecompileCall) instruction with the provided params.
134    pub fn from_precompile_call(
135        abi: Register1,
136        burn: Register2,
137        out: Register1,
138        arguments: Arguments,
139    ) -> Self {
140        Self {
141            arguments: arguments
142                .write_source(&abi)
143                .write_source(&burn)
144                .write_destination(&out),
145            handler: precompile_call,
146        }
147    }
148}