Skip to main content

cairo_vm/vm/runners/builtin_runner/
range_check.rs

1use std::cmp::{max, min};
2
3use crate::{
4    air_private_input::{PrivateInput, PrivateInputValue},
5    types::{builtin_name::BuiltinName, instance_definitions::LowRatio},
6};
7
8use crate::Felt252;
9use crate::{
10    types::relocatable::{MaybeRelocatable, Relocatable},
11    vm::{
12        errors::memory_errors::MemoryError,
13        vm_memory::{
14            memory::{Memory, ValidationRule},
15            memory_segments::MemorySegmentManager,
16        },
17    },
18};
19
20use lazy_static::lazy_static;
21
22const INNER_RC_BOUND_SHIFT: u64 = 16;
23const INNER_RC_BOUND_MASK: u64 = u16::MAX as u64;
24
25pub const RC_N_PARTS_STANDARD: u64 = 8;
26pub const RC_N_PARTS_96: u64 = 6;
27
28lazy_static! {
29    pub static ref BOUND_STANDARD: Felt252 =
30        Felt252::TWO.pow(INNER_RC_BOUND_SHIFT * RC_N_PARTS_STANDARD);
31    pub static ref BOUND_96: Felt252 = Felt252::TWO.pow(INNER_RC_BOUND_SHIFT * RC_N_PARTS_96);
32}
33
34#[derive(Debug, Clone)]
35pub struct RangeCheckBuiltinRunner<const N_PARTS: u64> {
36    ratio: Option<LowRatio>,
37    base: usize,
38    pub(crate) stop_ptr: Option<usize>,
39    pub(crate) included: bool,
40}
41
42impl<const N_PARTS: u64> RangeCheckBuiltinRunner<N_PARTS> {
43    pub fn new(ratio: Option<u32>, included: bool) -> RangeCheckBuiltinRunner<N_PARTS> {
44        RangeCheckBuiltinRunner {
45            ratio: ratio.map(LowRatio::new_int),
46            base: 0,
47            stop_ptr: None,
48            included,
49        }
50    }
51
52    pub fn new_with_low_ratio(
53        ratio: Option<LowRatio>,
54        included: bool,
55    ) -> RangeCheckBuiltinRunner<N_PARTS> {
56        RangeCheckBuiltinRunner {
57            ratio,
58            base: 0,
59            stop_ptr: None,
60            included,
61        }
62    }
63
64    pub fn initialize_segments(&mut self, segments: &mut MemorySegmentManager) {
65        self.base = segments.add().segment_index as usize // segments.add() always returns a positive index
66    }
67
68    pub fn initial_stack(&self) -> Vec<MaybeRelocatable> {
69        if self.included {
70            vec![MaybeRelocatable::from((self.base as isize, 0))]
71        } else {
72            vec![]
73        }
74    }
75
76    pub fn base(&self) -> usize {
77        self.base
78    }
79
80    pub fn ratio(&self) -> Option<u32> {
81        self.ratio.map(|ratio| ratio.numerator)
82    }
83
84    pub fn ratio_den(&self) -> Option<u32> {
85        self.ratio.map(|ratio| ratio.denominator)
86    }
87
88    pub fn name(&self) -> BuiltinName {
89        match N_PARTS {
90            RC_N_PARTS_96 => BuiltinName::range_check96,
91            _ => BuiltinName::range_check,
92        }
93    }
94
95    pub fn n_parts(&self) -> u64 {
96        N_PARTS
97    }
98
99    pub fn bound(&self) -> &'static Felt252 {
100        match N_PARTS {
101            RC_N_PARTS_96 => &BOUND_96,
102            _ => &BOUND_STANDARD,
103        }
104    }
105
106    pub fn add_validation_rule(&self, memory: &mut Memory) {
107        let rule = ValidationRule(Box::new(
108            |memory: &Memory, address: Relocatable| -> Result<Vec<Relocatable>, MemoryError> {
109                let num = memory
110                    .get_integer(address)
111                    .map_err(|_| MemoryError::RangeCheckFoundNonInt(Box::new(address)))?;
112                if num.bits() as u64 <= N_PARTS * INNER_RC_BOUND_SHIFT {
113                    Ok(vec![address.to_owned()])
114                } else {
115                    Err(MemoryError::RangeCheckNumOutOfBounds(Box::new((
116                        num.into_owned(),
117                        Felt252::TWO.pow((N_PARTS * INNER_RC_BOUND_SHIFT) as u128),
118                    ))))
119                }
120            },
121        ));
122        memory.add_validation_rule(self.base, rule);
123    }
124
125    pub fn get_used_cells(&self, segments: &MemorySegmentManager) -> Result<usize, MemoryError> {
126        segments
127            .get_segment_used_size(self.base)
128            .ok_or(MemoryError::MissingSegmentUsedSizes)
129    }
130
131    pub fn get_range_check_usage(&self, memory: &Memory) -> Option<(usize, usize)> {
132        let range_check_segment = memory.data.get(self.base)?;
133        let mut rc_bounds =
134            (!range_check_segment.is_empty()).then_some((usize::MAX, usize::MIN))?;
135
136        // Split value into n_parts parts of less than _INNER_RC_BOUND size.
137        for value in range_check_segment {
138            rc_bounds = value
139                .get_value()?
140                .get_int_ref()?
141                .to_le_digits()
142                // TODO: maybe skip leading zeros
143                .into_iter()
144                .flat_map(|digit| {
145                    (0..=3)
146                        .rev()
147                        .map(move |i| ((digit >> (i * INNER_RC_BOUND_SHIFT)) & INNER_RC_BOUND_MASK))
148                })
149                .take(N_PARTS as usize)
150                .fold(rc_bounds, |mm, x| {
151                    (min(mm.0, x as usize), max(mm.1, x as usize))
152                });
153        }
154        Some(rc_bounds)
155    }
156
157    pub fn get_used_instances(
158        &self,
159        segments: &MemorySegmentManager,
160    ) -> Result<usize, MemoryError> {
161        self.get_used_cells(segments)
162    }
163
164    pub fn air_private_input(&self, memory: &Memory) -> Vec<PrivateInput> {
165        let mut private_inputs = vec![];
166        if let Some(segment) = memory.data.get(self.base) {
167            for (index, cell) in segment.iter().enumerate() {
168                if let Some(value) = cell.get_value().and_then(|value| value.get_int()) {
169                    private_inputs.push(PrivateInput::Value(PrivateInputValue { index, value }))
170                }
171            }
172        }
173        private_inputs
174    }
175}
176
177#[cfg(test)]
178mod tests {
179    use super::*;
180    use crate::relocatable;
181    use crate::types::builtin_name::BuiltinName;
182    use crate::vm::errors::runner_errors::RunnerError;
183    use crate::vm::vm_memory::memory::Memory;
184    use crate::{
185        hint_processor::builtin_hint_processor::builtin_hint_processor_definition::BuiltinHintProcessor,
186        types::program::Program, utils::test_utils::*, vm::runners::builtin_runner::BuiltinRunner,
187    };
188
189    #[test]
190    fn get_used_instances() {
191        let builtin = RangeCheckBuiltinRunner::<RC_N_PARTS_STANDARD>::new(Some(10), true);
192
193        let mut vm = vm!();
194        vm.segments.segment_used_sizes = Some(vec![1]);
195
196        assert_eq!(builtin.get_used_instances(&vm.segments), Ok(1));
197    }
198
199    #[test]
200    fn final_stack() {
201        let mut builtin: BuiltinRunner =
202            RangeCheckBuiltinRunner::<RC_N_PARTS_STANDARD>::new(Some(10), true).into();
203
204        let mut vm = vm!();
205
206        vm.segments = segments![
207            ((0, 0), (0, 0)),
208            ((0, 1), (0, 1)),
209            ((2, 0), (0, 0)),
210            ((2, 1), (0, 0))
211        ];
212
213        vm.segments.segment_used_sizes = Some(vec![0]);
214
215        let pointer = Relocatable::from((2, 2));
216
217        assert_eq!(
218            builtin.final_stack(&vm.segments, pointer).unwrap(),
219            Relocatable::from((2, 1))
220        );
221    }
222
223    #[test]
224    fn final_stack_error_stop_pointer() {
225        let mut builtin: BuiltinRunner =
226            RangeCheckBuiltinRunner::<RC_N_PARTS_STANDARD>::new(Some(10), true).into();
227
228        let mut vm = vm!();
229
230        vm.segments = segments![
231            ((0, 0), (0, 0)),
232            ((0, 1), (0, 1)),
233            ((2, 0), (0, 0)),
234            ((2, 1), (0, 0))
235        ];
236
237        vm.segments.segment_used_sizes = Some(vec![998]);
238
239        let pointer = Relocatable::from((2, 2));
240
241        assert_eq!(
242            builtin.final_stack(&vm.segments, pointer),
243            Err(RunnerError::InvalidStopPointer(Box::new((
244                BuiltinName::range_check,
245                relocatable!(0, 998),
246                relocatable!(0, 0)
247            ))))
248        );
249    }
250
251    #[test]
252    fn final_stack_error_when_notincluded() {
253        let mut builtin: BuiltinRunner =
254            RangeCheckBuiltinRunner::<RC_N_PARTS_STANDARD>::new(Some(10), false).into();
255
256        let mut vm = vm!();
257
258        vm.segments = segments![
259            ((0, 0), (0, 0)),
260            ((0, 1), (0, 1)),
261            ((2, 0), (0, 0)),
262            ((2, 1), (0, 0))
263        ];
264
265        vm.segments.segment_used_sizes = Some(vec![0]);
266
267        let pointer = Relocatable::from((2, 2));
268
269        assert_eq!(
270            builtin.final_stack(&vm.segments, pointer).unwrap(),
271            Relocatable::from((2, 2))
272        );
273    }
274
275    #[test]
276    fn final_stack_error_non_relocatable() {
277        let mut builtin: BuiltinRunner =
278            RangeCheckBuiltinRunner::<RC_N_PARTS_STANDARD>::new(Some(10), true).into();
279
280        let mut vm = vm!();
281
282        vm.segments = segments![
283            ((0, 0), (0, 0)),
284            ((0, 1), (0, 1)),
285            ((2, 0), (0, 0)),
286            ((2, 1), 2)
287        ];
288
289        vm.segments.segment_used_sizes = Some(vec![0]);
290
291        let pointer = Relocatable::from((2, 2));
292
293        assert_eq!(
294            builtin.final_stack(&vm.segments, pointer),
295            Err(RunnerError::NoStopPointer(Box::new(
296                BuiltinName::range_check
297            )))
298        );
299    }
300
301    #[test]
302    fn get_used_cells_and_allocated_size_test() {
303        let builtin: BuiltinRunner =
304            RangeCheckBuiltinRunner::<RC_N_PARTS_STANDARD>::new(Some(10), true).into();
305
306        let program = program!(
307            builtins = vec![BuiltinName::range_check],
308            data = vec_data!(
309                (4612671182993129469_i64),
310                (5189976364521848832_i64),
311                (18446744073709551615_i128),
312                (5199546496550207487_i64),
313                (4612389712311386111_i64),
314                (5198983563776393216_i64),
315                (2),
316                (2345108766317314046_i64),
317                (5191102247248822272_i64),
318                (5189976364521848832_i64),
319                (7),
320                (1226245742482522112_i64),
321                ((
322                    "3618502788666131213697322783095070105623107215331596699973092056135872020470",
323                    10
324                )),
325                (2345108766317314046_i64)
326            ),
327            main = Some(8),
328        );
329
330        let mut cairo_runner = cairo_runner!(program);
331
332        cairo_runner.vm.segments.segment_used_sizes = Some(vec![0]);
333
334        let mut hint_processor = BuiltinHintProcessor::new_empty();
335
336        let address = cairo_runner.initialize(false).unwrap();
337
338        cairo_runner
339            .run_until_pc(address, &mut hint_processor)
340            .unwrap();
341
342        assert_eq!(
343            builtin.get_used_cells_and_allocated_size(&cairo_runner.vm),
344            Ok((0, 1))
345        );
346    }
347
348    #[test]
349    fn get_allocated_memory_units() {
350        let builtin: BuiltinRunner =
351            RangeCheckBuiltinRunner::<RC_N_PARTS_STANDARD>::new(Some(10), true).into();
352
353        let program = program!(
354            builtins = vec![BuiltinName::range_check],
355            data = vec_data!(
356                (4612671182993129469_i64),
357                (5189976364521848832_i64),
358                (18446744073709551615_i128),
359                (5199546496550207487_i64),
360                (4612389712311386111_i64),
361                (5198983563776393216_i64),
362                (2),
363                (2345108766317314046_i64),
364                (5191102247248822272_i64),
365                (5189976364521848832_i64),
366                (7),
367                (1226245742482522112_i64),
368                ((
369                    "3618502788666131213697322783095070105623107215331596699973092056135872020470",
370                    10
371                )),
372                (2345108766317314046_i64)
373            ),
374            main = Some(8),
375        );
376
377        let mut cairo_runner = cairo_runner!(program);
378
379        let mut hint_processor = BuiltinHintProcessor::new_empty();
380
381        let address = cairo_runner.initialize(false).unwrap();
382
383        cairo_runner
384            .run_until_pc(address, &mut hint_processor)
385            .unwrap();
386
387        assert_eq!(builtin.get_allocated_memory_units(&cairo_runner.vm), Ok(1));
388    }
389
390    #[test]
391    fn initialize_segments_for_range_check() {
392        let mut builtin = RangeCheckBuiltinRunner::<RC_N_PARTS_STANDARD>::new(Some(8), true);
393        let mut segments = MemorySegmentManager::new();
394        builtin.initialize_segments(&mut segments);
395        assert_eq!(builtin.base, 0);
396    }
397
398    #[test]
399    fn get_initial_stack_for_range_check_with_base() {
400        let mut builtin = RangeCheckBuiltinRunner::<RC_N_PARTS_STANDARD>::new(Some(8), true);
401        builtin.base = 1;
402        let initial_stack = builtin.initial_stack();
403        assert_eq!(
404            initial_stack[0].clone(),
405            MaybeRelocatable::RelocatableValue((builtin.base() as isize, 0).into())
406        );
407        assert_eq!(initial_stack.len(), 1);
408    }
409
410    #[test]
411    fn test_base() {
412        let builtin = RangeCheckBuiltinRunner::<RC_N_PARTS_STANDARD>::new(Some(8), true);
413        assert_eq!(builtin.base(), 0);
414    }
415
416    #[test]
417    fn test_ratio() {
418        let builtin = RangeCheckBuiltinRunner::<RC_N_PARTS_STANDARD>::new(Some(8), true);
419        assert_eq!(builtin.ratio(), Some(8));
420    }
421
422    #[test]
423    fn get_used_cells_missing_segment_used_sizes() {
424        let builtin = BuiltinRunner::RangeCheck(
425            RangeCheckBuiltinRunner::<RC_N_PARTS_STANDARD>::new(Some(256), true),
426        );
427        let vm = vm!();
428
429        assert_eq!(
430            builtin.get_used_cells(&vm.segments),
431            Err(MemoryError::MissingSegmentUsedSizes)
432        );
433    }
434
435    #[test]
436    fn get_used_cells_empty() {
437        let builtin = BuiltinRunner::RangeCheck(
438            RangeCheckBuiltinRunner::<RC_N_PARTS_STANDARD>::new(Some(256), true),
439        );
440        let mut vm = vm!();
441
442        vm.segments.segment_used_sizes = Some(vec![0]);
443        assert_eq!(builtin.get_used_cells(&vm.segments), Ok(0));
444    }
445
446    #[test]
447    fn get_used_cells() {
448        let builtin = BuiltinRunner::RangeCheck(
449            RangeCheckBuiltinRunner::<RC_N_PARTS_STANDARD>::new(Some(256), true),
450        );
451        let mut vm = vm!();
452
453        vm.segments.segment_used_sizes = Some(vec![4]);
454        assert_eq!(builtin.get_used_cells(&vm.segments), Ok(4));
455    }
456
457    #[test]
458    fn get_range_check_usage_succesful_a() {
459        let builtin = RangeCheckBuiltinRunner::<RC_N_PARTS_STANDARD>::new(Some(8), true);
460        let memory = memory![((0, 0), 1), ((0, 1), 2), ((0, 2), 3), ((0, 3), 4)];
461        assert_eq!(builtin.get_range_check_usage(&memory), Some((0, 4)));
462    }
463
464    #[test]
465    fn get_range_check_usage_succesful_b() {
466        let builtin = RangeCheckBuiltinRunner::<RC_N_PARTS_STANDARD>::new(Some(8), true);
467        let memory = memory![
468            ((0, 0), 1465218365),
469            ((0, 1), 2134570341),
470            ((0, 2), 31349610736_i64),
471            ((0, 3), 413468326585859_i64)
472        ];
473        assert_eq!(builtin.get_range_check_usage(&memory), Some((0, 62821)));
474    }
475
476    #[test]
477    fn get_range_check_usage_succesful_c() {
478        let builtin = RangeCheckBuiltinRunner::<RC_N_PARTS_STANDARD>::new(Some(8), true);
479        let memory = memory![
480            ((0, 0), 634834751465218365_i64),
481            ((0, 1), 42876922134570341_i64),
482            ((0, 2), 23469831349610736_i64),
483            ((0, 3), 23468413468326585859_i128),
484            ((0, 4), 75346043276073460326_i128),
485            ((0, 5), 87234598724867609478353436890268_i128)
486        ];
487        assert_eq!(builtin.get_range_check_usage(&memory), Some((0, 61576)));
488    }
489
490    #[test]
491    fn get_range_check_empty_memory() {
492        let builtin = RangeCheckBuiltinRunner::<RC_N_PARTS_STANDARD>::new(Some(8), true);
493        let memory = Memory::new();
494        assert_eq!(builtin.get_range_check_usage(&memory), None);
495    }
496
497    /// Test that the method get_used_perm_range_check_units works as intended.
498    #[test]
499    fn get_used_perm_range_check_units() {
500        let builtin_runner: BuiltinRunner =
501            RangeCheckBuiltinRunner::<RC_N_PARTS_STANDARD>::new(Some(8), true).into();
502        let mut vm = vm!();
503
504        vm.current_step = 8;
505        vm.segments.segment_used_sizes = Some(vec![1]);
506        assert_eq!(builtin_runner.get_used_perm_range_check_units(&vm), Ok(8));
507    }
508
509    #[test]
510    fn get_air_private_input() {
511        let builtin: BuiltinRunner =
512            RangeCheckBuiltinRunner::<RC_N_PARTS_STANDARD>::new(None, true).into();
513
514        let segments = segments![((0, 0), 0), ((0, 1), 1), ((0, 2), 2)];
515        assert_eq!(
516            builtin.air_private_input(&segments),
517            (vec![
518                PrivateInput::Value(PrivateInputValue {
519                    index: 0,
520                    value: 0.into(),
521                }),
522                PrivateInput::Value(PrivateInputValue {
523                    index: 1,
524                    value: 1.into(),
525                }),
526                PrivateInput::Value(PrivateInputValue {
527                    index: 2,
528                    value: 2.into(),
529                }),
530            ]),
531        );
532    }
533}