cairo_vm/vm/runners/builtin_runner/
poseidon.rs

1use crate::air_private_input::{PrivateInput, PrivateInputPoseidonState};
2use crate::stdlib::{cell::RefCell, collections::HashMap, prelude::*};
3use crate::types::builtin_name::BuiltinName;
4use crate::types::instance_definitions::poseidon_instance_def::{
5    CELLS_PER_POSEIDON, INPUT_CELLS_PER_POSEIDON,
6};
7use crate::types::relocatable::{MaybeRelocatable, Relocatable};
8use crate::vm::errors::memory_errors::MemoryError;
9use crate::vm::errors::runner_errors::RunnerError;
10use crate::vm::vm_memory::memory::Memory;
11use crate::vm::vm_memory::memory_segments::MemorySegmentManager;
12use crate::Felt252;
13use num_integer::div_ceil;
14use starknet_types_core::hash::Poseidon;
15
16#[derive(Debug, Clone)]
17pub struct PoseidonBuiltinRunner {
18    pub base: usize,
19    ratio: Option<u32>,
20    pub(crate) stop_ptr: Option<usize>,
21    pub(crate) included: bool,
22    cache: RefCell<HashMap<Relocatable, Felt252>>,
23}
24
25impl PoseidonBuiltinRunner {
26    pub fn new(ratio: Option<u32>, included: bool) -> Self {
27        PoseidonBuiltinRunner {
28            base: 0,
29            ratio,
30            stop_ptr: None,
31            included,
32            cache: RefCell::new(HashMap::new()),
33        }
34    }
35
36    pub fn initialize_segments(&mut self, segments: &mut MemorySegmentManager) {
37        self.base = segments.add().segment_index as usize // segments.add() always returns a positive index
38    }
39
40    pub fn initial_stack(&self) -> Vec<MaybeRelocatable> {
41        if self.included {
42            vec![MaybeRelocatable::from((self.base as isize, 0))]
43        } else {
44            vec![]
45        }
46    }
47
48    pub fn base(&self) -> usize {
49        self.base
50    }
51
52    pub fn ratio(&self) -> Option<u32> {
53        self.ratio
54    }
55
56    pub fn add_validation_rule(&self, _memory: &mut Memory) {}
57
58    pub fn deduce_memory_cell(
59        &self,
60        address: Relocatable,
61        memory: &Memory,
62    ) -> Result<Option<MaybeRelocatable>, RunnerError> {
63        let index = address.offset % CELLS_PER_POSEIDON as usize;
64        if index < INPUT_CELLS_PER_POSEIDON as usize {
65            return Ok(None);
66        }
67        if let Some(felt) = self.cache.borrow().get(&address) {
68            return Ok(Some(felt.into()));
69        }
70        let first_input_addr = (address - index)?;
71        let first_output_addr = (first_input_addr + INPUT_CELLS_PER_POSEIDON as usize)?;
72
73        let mut input_felts = vec![];
74
75        for i in 0..INPUT_CELLS_PER_POSEIDON as usize {
76            let m_index = (first_input_addr + i)?;
77            let val = match memory.get(&m_index) {
78                Some(value) => *value
79                    .get_int_ref()
80                    .ok_or(RunnerError::BuiltinExpectedInteger(Box::new((
81                        BuiltinName::poseidon,
82                        m_index,
83                    ))))?,
84                _ => return Ok(None),
85            };
86            input_felts.push(val)
87        }
88        // n_input_cells is fixed to 3, so this try_into will never fail
89        let mut poseidon_state: [Felt252; 3] = input_felts.try_into().unwrap();
90        Poseidon::hades_permutation(&mut poseidon_state);
91        for (i, elem) in poseidon_state.iter().enumerate() {
92            self.cache
93                .borrow_mut()
94                .insert((first_output_addr + i)?, *elem);
95        }
96
97        Ok(self.cache.borrow().get(&address).map(|x| x.into()))
98    }
99
100    pub fn get_used_cells(&self, segments: &MemorySegmentManager) -> Result<usize, MemoryError> {
101        segments
102            .get_segment_used_size(self.base())
103            .ok_or(MemoryError::MissingSegmentUsedSizes)
104    }
105
106    pub fn get_used_instances(
107        &self,
108        segments: &MemorySegmentManager,
109    ) -> Result<usize, MemoryError> {
110        let used_cells = self.get_used_cells(segments)?;
111        Ok(div_ceil(used_cells, CELLS_PER_POSEIDON as usize))
112    }
113
114    pub fn air_private_input(&self, memory: &Memory) -> Vec<PrivateInput> {
115        let mut private_inputs = vec![];
116        if let Some(segment) = memory.data.get(self.base) {
117            let segment_len = segment.len();
118            for (index, off) in (0..segment_len)
119                .step_by(CELLS_PER_POSEIDON as usize)
120                .enumerate()
121            {
122                // Add the input cells of each poseidon instance to the private inputs
123                if let (Ok(input_s0), Ok(input_s1), Ok(input_s2)) = (
124                    memory.get_integer((self.base as isize, off).into()),
125                    memory.get_integer((self.base as isize, off + 1).into()),
126                    memory.get_integer((self.base as isize, off + 2).into()),
127                ) {
128                    private_inputs.push(PrivateInput::PoseidonState(PrivateInputPoseidonState {
129                        index,
130                        input_s0: *input_s0,
131                        input_s1: *input_s1,
132                        input_s2: *input_s2,
133                    }))
134                }
135            }
136        }
137        private_inputs
138    }
139}
140
141#[cfg(test)]
142mod tests {
143    use super::*;
144    use crate::hint_processor::builtin_hint_processor::builtin_hint_processor_definition::BuiltinHintProcessor;
145    use crate::relocatable;
146    use crate::types::builtin_name::BuiltinName;
147    use crate::types::program::Program;
148    use crate::utils::test_utils::*;
149
150    use crate::vm::runners::builtin_runner::BuiltinRunner;
151    #[cfg(target_arch = "wasm32")]
152    use wasm_bindgen_test::*;
153
154    #[test]
155    #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
156    fn get_used_instances() {
157        let builtin = PoseidonBuiltinRunner::new(Some(10), true);
158
159        let mut vm = vm!();
160        vm.segments.segment_used_sizes = Some(vec![1]);
161
162        assert_eq!(builtin.get_used_instances(&vm.segments), Ok(1));
163    }
164
165    #[test]
166    #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
167    fn get_used_instances_enum() {
168        let builtin: BuiltinRunner = PoseidonBuiltinRunner::new(Some(10), true).into();
169
170        let mut vm = vm!();
171        vm.segments.segment_used_sizes = Some(vec![1]);
172
173        assert_eq!(builtin.get_used_instances(&vm.segments), Ok(1));
174    }
175
176    #[test]
177    #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
178    fn final_stack() {
179        let mut builtin: BuiltinRunner = PoseidonBuiltinRunner::new(Some(10), true).into();
180
181        let mut vm = vm!();
182
183        vm.segments = segments![
184            ((0, 0), (0, 0)),
185            ((0, 1), (0, 1)),
186            ((2, 0), (0, 0)),
187            ((2, 1), (0, 0))
188        ];
189
190        vm.segments.segment_used_sizes = Some(vec![0]);
191
192        let pointer = Relocatable::from((2, 2));
193
194        assert_eq!(
195            builtin.final_stack(&vm.segments, pointer).unwrap(),
196            Relocatable::from((2, 1))
197        );
198    }
199
200    #[test]
201    #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
202    fn final_stack_error_stop_pointer() {
203        let mut builtin: BuiltinRunner = PoseidonBuiltinRunner::new(Some(10), true).into();
204
205        let mut vm = vm!();
206
207        vm.segments = segments![
208            ((0, 0), (0, 0)),
209            ((0, 1), (0, 1)),
210            ((2, 0), (0, 0)),
211            ((2, 1), (0, 0))
212        ];
213
214        vm.segments.segment_used_sizes = Some(vec![999]);
215
216        let pointer = Relocatable::from((2, 2));
217
218        assert_eq!(
219            builtin.final_stack(&vm.segments, pointer),
220            Err(RunnerError::InvalidStopPointer(Box::new((
221                BuiltinName::poseidon,
222                relocatable!(0, 1002),
223                relocatable!(0, 0)
224            ))))
225        );
226    }
227
228    #[test]
229    #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
230    fn final_stack_error_when_not_included() {
231        let mut builtin: BuiltinRunner = PoseidonBuiltinRunner::new(Some(10), false).into();
232
233        let mut vm = vm!();
234
235        vm.segments = segments![
236            ((0, 0), (0, 0)),
237            ((0, 1), (0, 1)),
238            ((2, 0), (0, 0)),
239            ((2, 1), (0, 0))
240        ];
241
242        vm.segments.segment_used_sizes = Some(vec![0]);
243
244        let pointer = Relocatable::from((2, 2));
245
246        assert_eq!(
247            builtin.final_stack(&vm.segments, pointer).unwrap(),
248            Relocatable::from((2, 2))
249        );
250    }
251
252    #[test]
253    #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
254    fn final_stack_error_non_relocatable() {
255        let mut builtin: BuiltinRunner = PoseidonBuiltinRunner::new(Some(10), true).into();
256
257        let mut vm = vm!();
258
259        vm.segments = segments![
260            ((0, 0), (0, 0)),
261            ((0, 1), (0, 1)),
262            ((2, 0), (0, 0)),
263            ((2, 1), 2)
264        ];
265
266        vm.segments.segment_used_sizes = Some(vec![0]);
267
268        let pointer = Relocatable::from((2, 2));
269
270        assert_eq!(
271            builtin.final_stack(&vm.segments, pointer),
272            Err(RunnerError::NoStopPointer(Box::new(BuiltinName::poseidon)))
273        );
274    }
275
276    #[test]
277    #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
278    fn get_used_cells_and_allocated_size_test() {
279        let builtin: BuiltinRunner = PoseidonBuiltinRunner::new(Some(10), true).into();
280
281        let program = program!(
282            builtins = vec![BuiltinName::poseidon],
283            data = vec_data!(
284                (4612671182993129469_i64),
285                (5189976364521848832_i64),
286                (18446744073709551615_i128),
287                (5199546496550207487_i64),
288                (4612389712311386111_i64),
289                (5198983563776393216_i64),
290                (2),
291                (2345108766317314046_i64),
292                (5191102247248822272_i64),
293                (5189976364521848832_i64),
294                (7),
295                (1226245742482522112_i64),
296                ((
297                    "3618502788666131213697322783095070105623107215331596699973092056135872020470",
298                    10
299                )),
300                (2345108766317314046_i64)
301            ),
302            main = Some(8),
303        );
304
305        let mut cairo_runner = cairo_runner!(program);
306        cairo_runner.vm.segments.segment_used_sizes = Some(vec![0]);
307
308        let mut hint_processor = BuiltinHintProcessor::new_empty();
309
310        let address = cairo_runner.initialize(false).unwrap();
311
312        cairo_runner
313            .run_until_pc(address, &mut hint_processor)
314            .unwrap();
315
316        assert_eq!(
317            builtin.get_used_cells_and_allocated_size(&cairo_runner.vm),
318            Ok((0, 6))
319        );
320    }
321
322    #[test]
323    #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
324    fn get_allocated_memory_units() {
325        let builtin: BuiltinRunner = PoseidonBuiltinRunner::new(Some(10), true).into();
326
327        let program = program!(
328            builtins = vec![BuiltinName::poseidon],
329            data = vec_data!(
330                (4612671182993129469_i64),
331                (5189976364521848832_i64),
332                (18446744073709551615_i128),
333                (5199546496550207487_i64),
334                (4612389712311386111_i64),
335                (5198983563776393216_i64),
336                (2),
337                (2345108766317314046_i64),
338                (5191102247248822272_i64),
339                (5189976364521848832_i64),
340                (7),
341                (1226245742482522112_i64),
342                ((
343                    "3618502788666131213697322783095070105623107215331596699973092056135872020470",
344                    10
345                )),
346                (2345108766317314046_i64)
347            ),
348            main = Some(8),
349        );
350
351        let mut cairo_runner = cairo_runner!(program);
352
353        let mut hint_processor = BuiltinHintProcessor::new_empty();
354
355        let address = cairo_runner.initialize(false).unwrap();
356
357        cairo_runner
358            .run_until_pc(address, &mut hint_processor)
359            .unwrap();
360
361        assert_eq!(builtin.get_allocated_memory_units(&cairo_runner.vm), Ok(6));
362    }
363
364    #[test]
365    #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
366    fn deduce_memory_cell_missing_input_cells_ok() {
367        let builtin = PoseidonBuiltinRunner::new(Some(10), false);
368
369        let mut vm = vm!();
370
371        vm.segments = segments![
372            ((0, 0), (0, 0)),
373            ((0, 1), (0, 1)),
374            ((2, 0), (0, 0)),
375            ((2, 1), (0, 0))
376        ];
377
378        assert_eq!(
379            builtin.deduce_memory_cell(relocatable!(0, 1), &vm.segments.memory),
380            Ok(None)
381        );
382    }
383
384    #[test]
385    #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
386    fn get_air_private_input() {
387        let builtin: BuiltinRunner = PoseidonBuiltinRunner::new(None, true).into();
388
389        let segments = segments![
390            ((0, 0), 0),
391            ((0, 1), 1),
392            ((0, 2), 2),
393            ((0, 3), 3),
394            ((0, 4), 4),
395            ((0, 5), 5),
396            ((0, 6), 6),
397            ((0, 7), 7),
398            ((0, 8), 8),
399            ((0, 9), 9),
400            ((0, 10), 10),
401            ((0, 11), 11)
402        ];
403        assert_eq!(
404            builtin.air_private_input(&segments),
405            (vec![
406                PrivateInput::PoseidonState(PrivateInputPoseidonState {
407                    index: 0,
408                    input_s0: 0.into(),
409                    input_s1: 1.into(),
410                    input_s2: 2.into(),
411                }),
412                PrivateInput::PoseidonState(PrivateInputPoseidonState {
413                    index: 1,
414                    input_s0: 6.into(),
415                    input_s1: 7.into(),
416                    input_s2: 8.into(),
417                }),
418            ]),
419        );
420    }
421}