1#![expect(clippy::literal_string_with_formatting_args)]
2#[cfg(not(feature = "std"))]
3use alloc::{
4 format,
5 string::{String, ToString},
6};
7use core::fmt::{Display, Formatter};
8
9use cairo_lang_utils::bigint::BigIntAsHex;
10use indoc::formatdoc;
11
12use crate::operand::{CellRef, DerefOrImmediate, ResOperand};
13
14#[cfg(test)]
15mod test;
16
17#[derive(Debug, Eq, PartialEq, Clone)]
21#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize), serde(untagged))]
22#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
23#[cfg_attr(
24 feature = "parity-scale-codec",
25 derive(parity_scale_codec::Encode, parity_scale_codec::Decode)
26)]
27pub enum Hint {
28 #[cfg_attr(feature = "parity-scale-codec", codec(index = 0))]
29 Core(CoreHintBase),
30 #[cfg_attr(feature = "parity-scale-codec", codec(index = 1))]
31 Starknet(StarknetHint),
32 #[cfg_attr(feature = "parity-scale-codec", codec(index = 2))]
33 #[cfg_attr(feature = "schemars", schemars(skip))]
34 External(ExternalHint),
35}
36
37impl Hint {
38 pub fn representing_string(&self) -> String {
39 format!("{self:?}")
40 }
41}
42
43impl From<CoreHint> for Hint {
44 fn from(value: CoreHint) -> Self {
45 Hint::Core(value.into())
46 }
47}
48impl From<StarknetHint> for Hint {
49 fn from(value: StarknetHint) -> Self {
50 Hint::Starknet(value)
51 }
52}
53impl From<ExternalHint> for Hint {
54 fn from(value: ExternalHint) -> Self {
55 Hint::External(value)
56 }
57}
58
59pub trait PythonicHint {
62 fn get_pythonic_hint(&self) -> String;
63}
64
65impl PythonicHint for Hint {
66 fn get_pythonic_hint(&self) -> String {
67 match self {
68 Hint::Core(hint) => hint.get_pythonic_hint(),
69 Hint::Starknet(hint) => hint.get_pythonic_hint(),
70 Hint::External(hint) => hint.get_pythonic_hint(),
71 }
72 }
73}
74
75#[derive(Debug, Eq, PartialEq, Clone)]
77#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
78#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
79#[cfg_attr(
80 feature = "parity-scale-codec",
81 derive(parity_scale_codec::Encode, parity_scale_codec::Decode)
82)]
83pub enum StarknetHint {
84 #[cfg_attr(feature = "parity-scale-codec", codec(index = 0))]
85 SystemCall { system: ResOperand },
86 #[cfg_attr(feature = "parity-scale-codec", codec(index = 1))]
87 #[cfg_attr(feature = "schemars", schemars(skip))]
88 Cheatcode {
89 selector: BigIntAsHex,
90 input_start: ResOperand,
91 input_end: ResOperand,
92 output_start: CellRef,
93 output_end: CellRef,
94 },
95}
96
97#[derive(Debug, Eq, PartialEq, Clone)]
99#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize), serde(untagged))]
100#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
101#[cfg_attr(
102 feature = "parity-scale-codec",
103 derive(parity_scale_codec::Encode, parity_scale_codec::Decode)
104)]
105pub enum CoreHintBase {
106 #[cfg_attr(feature = "parity-scale-codec", codec(index = 0))]
107 Core(CoreHint),
108 #[cfg_attr(feature = "parity-scale-codec", codec(index = 1))]
109 Deprecated(DeprecatedHint),
110}
111
112impl From<CoreHint> for CoreHintBase {
113 fn from(value: CoreHint) -> Self {
114 CoreHintBase::Core(value)
115 }
116}
117impl From<DeprecatedHint> for CoreHintBase {
118 fn from(value: DeprecatedHint) -> Self {
119 CoreHintBase::Deprecated(value)
120 }
121}
122
123#[derive(Debug, Eq, PartialEq, Clone)]
124#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
125#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
126#[cfg_attr(
127 feature = "parity-scale-codec",
128 derive(parity_scale_codec::Encode, parity_scale_codec::Decode)
129)]
130pub enum CoreHint {
131 #[cfg_attr(feature = "parity-scale-codec", codec(index = 0))]
132 AllocSegment { dst: CellRef },
133 #[cfg_attr(feature = "parity-scale-codec", codec(index = 1))]
134 TestLessThan { lhs: ResOperand, rhs: ResOperand, dst: CellRef },
135 #[cfg_attr(feature = "parity-scale-codec", codec(index = 2))]
136 TestLessThanOrEqual { lhs: ResOperand, rhs: ResOperand, dst: CellRef },
137 #[cfg_attr(feature = "parity-scale-codec", codec(index = 28))]
139 TestLessThanOrEqualAddress { lhs: ResOperand, rhs: ResOperand, dst: CellRef },
140 #[cfg_attr(feature = "parity-scale-codec", codec(index = 3))]
143 WideMul128 { lhs: ResOperand, rhs: ResOperand, high: CellRef, low: CellRef },
144 #[cfg_attr(feature = "parity-scale-codec", codec(index = 4))]
148 DivMod { lhs: ResOperand, rhs: ResOperand, quotient: CellRef, remainder: CellRef },
149 #[cfg_attr(feature = "parity-scale-codec", codec(index = 5))]
153 Uint256DivMod {
154 dividend0: ResOperand,
155 dividend1: ResOperand,
156 divisor0: ResOperand,
157 divisor1: ResOperand,
158 quotient0: CellRef,
159 quotient1: CellRef,
160 remainder0: CellRef,
161 remainder1: CellRef,
162 },
163 #[cfg_attr(feature = "parity-scale-codec", codec(index = 6))]
168 Uint512DivModByUint256 {
169 dividend0: ResOperand,
170 dividend1: ResOperand,
171 dividend2: ResOperand,
172 dividend3: ResOperand,
173 divisor0: ResOperand,
174 divisor1: ResOperand,
175 quotient0: CellRef,
176 quotient1: CellRef,
177 quotient2: CellRef,
178 quotient3: CellRef,
179 remainder0: CellRef,
180 remainder1: CellRef,
181 },
182 #[cfg_attr(feature = "parity-scale-codec", codec(index = 7))]
183 SquareRoot { value: ResOperand, dst: CellRef },
184 #[cfg_attr(feature = "parity-scale-codec", codec(index = 8))]
189 Uint256SquareRoot {
190 value_low: ResOperand,
191 value_high: ResOperand,
192 sqrt0: CellRef,
193 sqrt1: CellRef,
194 remainder_low: CellRef,
195 remainder_high: CellRef,
196 sqrt_mul_2_minus_remainder_ge_u128: CellRef,
197 },
198 #[cfg_attr(feature = "parity-scale-codec", codec(index = 9))]
200 LinearSplit { value: ResOperand, scalar: ResOperand, max_x: ResOperand, x: CellRef, y: CellRef },
201 #[cfg_attr(feature = "parity-scale-codec", codec(index = 10))]
203 AllocFelt252Dict { segment_arena_ptr: ResOperand },
204 #[cfg_attr(feature = "parity-scale-codec", codec(index = 11))]
206 Felt252DictEntryInit { dict_ptr: ResOperand, key: ResOperand },
207 #[cfg_attr(feature = "parity-scale-codec", codec(index = 12))]
210 Felt252DictEntryUpdate { dict_ptr: ResOperand, value: ResOperand },
211 #[cfg_attr(feature = "parity-scale-codec", codec(index = 13))]
213 GetSegmentArenaIndex { dict_end_ptr: ResOperand, dict_index: CellRef },
214 #[cfg_attr(feature = "parity-scale-codec", codec(index = 14))]
216 InitSquashData {
217 dict_accesses: ResOperand,
218 ptr_diff: ResOperand,
219 n_accesses: ResOperand,
220 big_keys: CellRef,
221 first_key: CellRef,
222 },
223 #[cfg_attr(feature = "parity-scale-codec", codec(index = 15))]
225 GetCurrentAccessIndex { range_check_ptr: ResOperand },
226 #[cfg_attr(feature = "parity-scale-codec", codec(index = 16))]
228 ShouldSkipSquashLoop { should_skip_loop: CellRef },
229 #[cfg_attr(feature = "parity-scale-codec", codec(index = 17))]
231 GetCurrentAccessDelta { index_delta_minus1: CellRef },
232 #[cfg_attr(feature = "parity-scale-codec", codec(index = 18))]
234 ShouldContinueSquashLoop { should_continue: CellRef },
235 #[cfg_attr(feature = "parity-scale-codec", codec(index = 19))]
237 GetNextDictKey { next_key: CellRef },
238 #[cfg_attr(feature = "parity-scale-codec", codec(index = 20))]
241 AssertLeFindSmallArcs { range_check_ptr: ResOperand, a: ResOperand, b: ResOperand },
242 #[cfg_attr(feature = "parity-scale-codec", codec(index = 21))]
244 AssertLeIsFirstArcExcluded { skip_exclude_a_flag: CellRef },
245 #[cfg_attr(feature = "parity-scale-codec", codec(index = 22))]
247 AssertLeIsSecondArcExcluded { skip_exclude_b_minus_a: CellRef },
248 #[cfg_attr(feature = "parity-scale-codec", codec(index = 23))]
250 RandomEcPoint { x: CellRef, y: CellRef },
251 #[cfg_attr(feature = "parity-scale-codec", codec(index = 24))]
257 FieldSqrt { val: ResOperand, sqrt: CellRef },
258 #[cfg_attr(feature = "parity-scale-codec", codec(index = 25))]
261 DebugPrint { start: ResOperand, end: ResOperand },
262 #[cfg_attr(feature = "parity-scale-codec", codec(index = 26))]
264 AllocConstantSize { size: ResOperand, dst: CellRef },
265 #[cfg_attr(feature = "parity-scale-codec", codec(index = 27))]
285 U256InvModN {
286 b0: ResOperand,
287 b1: ResOperand,
288 n0: ResOperand,
289 n1: ResOperand,
290 g0_or_no_inv: CellRef,
291 g1_option: CellRef,
292 s_or_r0: CellRef,
293 s_or_r1: CellRef,
294 t_or_k0: CellRef,
295 t_or_k1: CellRef,
296 },
297
298 #[cfg_attr(feature = "parity-scale-codec", codec(index = 29))]
299 EvalCircuit {
300 n_add_mods: ResOperand,
301 add_mod_builtin: ResOperand,
302 n_mul_mods: ResOperand,
303 mul_mod_builtin: ResOperand,
304 },
305}
306
307#[derive(Debug, Eq, PartialEq, Clone)]
310#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
311#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
312#[cfg_attr(
313 feature = "parity-scale-codec",
314 derive(parity_scale_codec::Encode, parity_scale_codec::Decode)
315)]
316pub enum DeprecatedHint {
317 #[cfg_attr(feature = "parity-scale-codec", codec(index = 0))]
319 AssertCurrentAccessIndicesIsEmpty,
320 #[cfg_attr(feature = "parity-scale-codec", codec(index = 1))]
323 AssertAllAccessesUsed { n_used_accesses: CellRef },
324 #[cfg_attr(feature = "parity-scale-codec", codec(index = 2))]
326 AssertAllKeysUsed,
327 #[cfg_attr(feature = "parity-scale-codec", codec(index = 3))]
329 AssertLeAssertThirdArcExcluded,
330 #[cfg_attr(feature = "parity-scale-codec", codec(index = 4))]
332 AssertLtAssertValidInput { a: ResOperand, b: ResOperand },
333 #[cfg_attr(feature = "parity-scale-codec", codec(index = 5))]
336 Felt252DictRead { dict_ptr: ResOperand, key: ResOperand, value_dst: CellRef },
337 #[cfg_attr(feature = "parity-scale-codec", codec(index = 6))]
339 Felt252DictWrite { dict_ptr: ResOperand, key: ResOperand, value: ResOperand },
340}
341
342#[derive(Debug, Eq, PartialEq, Clone)]
346#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
347#[cfg_attr(
348 feature = "parity-scale-codec",
349 derive(parity_scale_codec::Encode, parity_scale_codec::Decode)
350)]
351pub enum ExternalHint {
352 #[cfg_attr(feature = "parity-scale-codec", codec(index = 0))]
354 AddRelocationRule { src: ResOperand, dst: ResOperand },
355 #[cfg_attr(feature = "parity-scale-codec", codec(index = 1))]
357 WriteRunParam { index: ResOperand, dst: CellRef },
358 #[cfg_attr(feature = "parity-scale-codec", codec(index = 2))]
360 AddMarker { start: ResOperand, end: ResOperand },
361 #[cfg_attr(feature = "parity-scale-codec", codec(index = 3))]
363 AddTrace { flag: ResOperand },
364}
365
366struct DerefOrImmediateFormatter<'a>(&'a DerefOrImmediate);
367impl Display for DerefOrImmediateFormatter<'_> {
368 fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
369 match self.0 {
370 DerefOrImmediate::Deref(d) => write!(f, "memory{d}"),
371 DerefOrImmediate::Immediate(i) => write!(f, "{}", i.value),
372 }
373 }
374}
375
376struct ResOperandAsIntegerFormatter<'a>(&'a ResOperand);
377impl Display for ResOperandAsIntegerFormatter<'_> {
378 fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
379 match self.0 {
380 ResOperand::Deref(d) => write!(f, "memory{d}"),
381 ResOperand::DoubleDeref(d, i) => write!(f, "memory[memory{d} + {i}]"),
382 ResOperand::Immediate(i) => write!(f, "{}", i.value),
383 ResOperand::BinOp(bin_op) => {
384 write!(
385 f,
386 "(memory{} {} {}) % PRIME",
387 bin_op.a,
388 bin_op.op,
389 DerefOrImmediateFormatter(&bin_op.b)
390 )
391 }
392 }
393 }
394}
395
396struct ResOperandAsAddressFormatter<'a>(&'a ResOperand);
397impl Display for ResOperandAsAddressFormatter<'_> {
398 fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
399 match self.0 {
400 ResOperand::Deref(d) => write!(f, "memory{d}"),
401 ResOperand::DoubleDeref(d, i) => write!(f, "memory[memory{d} + {i}]"),
402 ResOperand::Immediate(i) => {
403 unreachable!("Address cannot be an immediate: {}.", i.value)
404 }
405 ResOperand::BinOp(bin_op) => {
406 write!(
407 f,
408 "memory{} {} {}",
409 bin_op.a,
410 bin_op.op,
411 DerefOrImmediateFormatter(&bin_op.b)
412 )
413 }
414 }
415 }
416}
417
418impl PythonicHint for CoreHintBase {
419 fn get_pythonic_hint(&self) -> String {
420 match self {
421 CoreHintBase::Core(hint) => hint.get_pythonic_hint(),
422 CoreHintBase::Deprecated(_) => {
423 unreachable!("Deprecated hints do not have a pythonic version.")
424 }
425 }
426 }
427}
428
429impl PythonicHint for CoreHint {
430 fn get_pythonic_hint(&self) -> String {
431 match self {
432 CoreHint::AllocSegment { dst } => format!("memory{dst} = segments.add()"),
433 CoreHint::AllocFelt252Dict { segment_arena_ptr } => {
434 let segment_arena_ptr = ResOperandAsAddressFormatter(segment_arena_ptr);
435 formatdoc! {"
436
437 if '__dict_manager' not in globals():
438 from starkware.cairo.common.dict import DictManager
439 __dict_manager = DictManager()
440
441 if '__segment_index_to_arena_index' not in globals():
442 # A map from the relocatable value segment index to the index in the
443 # arena.
444 __segment_index_to_arena_index = {{}}
445
446 # {segment_arena_ptr} is the address of the next SegmentArenaBuiltin.
447 # memory[{segment_arena_ptr} - 2] is the number of allocated segments.
448 index = memory[{segment_arena_ptr} - 2]
449
450 segment_start = __dict_manager.new_default_dict(
451 segments, 0, temp_segment=index > 0
452 )
453
454 # Update '__segment_index_to_arena_index'.
455 __segment_index_to_arena_index[segment_start.segment_index] = index
456
457 # Update 'SegmentInfo::start'.
458 # memory[{segment_arena_ptr} - 3] is the address of the segment arena infos
459 # segment. index * 3 is added to get the address of the new SegmentInfo.
460 memory[memory[{segment_arena_ptr} - 3] + index * 3] = segment_start
461 "}
462 }
463 CoreHint::Felt252DictEntryInit { dict_ptr, key } => {
464 let (dict_ptr, key) =
465 (ResOperandAsAddressFormatter(dict_ptr), ResOperandAsIntegerFormatter(key));
466 formatdoc! {"
467
468 dict_tracker = __dict_manager.get_tracker({dict_ptr})
469 dict_tracker.current_ptr += 3
470 memory[{dict_ptr} + 1] = dict_tracker.data[{key}]
471 "}
472 }
473 CoreHint::Felt252DictEntryUpdate { dict_ptr, value } => {
474 let (dict_ptr, value) =
475 (ResOperandAsAddressFormatter(dict_ptr), ResOperandAsIntegerFormatter(value));
476 formatdoc! {"
477
478 dict_tracker = __dict_manager.get_tracker({dict_ptr})
479 dict_tracker.data[memory[{dict_ptr} - 3]] = {value}
480 "}
481 }
482 CoreHint::TestLessThan { lhs, rhs, dst } => {
483 format!(
484 "memory{dst} = {} < {}",
485 ResOperandAsIntegerFormatter(lhs),
486 ResOperandAsIntegerFormatter(rhs)
487 )
488 }
489 CoreHint::TestLessThanOrEqual { lhs, rhs, dst } => format!(
490 "memory{dst} = {} <= {}",
491 ResOperandAsIntegerFormatter(lhs),
492 ResOperandAsIntegerFormatter(rhs)
493 ),
494 CoreHint::TestLessThanOrEqualAddress { lhs, rhs, dst } => format!(
495 "memory{dst} = {} <= {}",
496 ResOperandAsAddressFormatter(lhs),
497 ResOperandAsAddressFormatter(rhs)
498 ),
499 CoreHint::WideMul128 { lhs, rhs, high, low } => format!(
500 "(memory{high}, memory{low}) = divmod({} * {}, 2**128)",
501 ResOperandAsIntegerFormatter(lhs),
502 ResOperandAsIntegerFormatter(rhs)
503 ),
504 CoreHint::DivMod { lhs, rhs, quotient, remainder } => format!(
505 "(memory{quotient}, memory{remainder}) = divmod({}, {})",
506 ResOperandAsIntegerFormatter(lhs),
507 ResOperandAsIntegerFormatter(rhs)
508 ),
509 CoreHint::Uint256DivMod {
510 dividend0,
511 dividend1,
512 quotient0,
513 quotient1,
514 divisor0,
515 divisor1,
516 remainder0,
517 remainder1,
518 } => {
519 let (dividend0, dividend1, divisor0, divisor1) = (
520 ResOperandAsIntegerFormatter(dividend0),
521 ResOperandAsIntegerFormatter(dividend1),
522 ResOperandAsIntegerFormatter(divisor0),
523 ResOperandAsIntegerFormatter(divisor1),
524 );
525 formatdoc! {"
526
527 dividend = {dividend0} + {dividend1} * 2**128
528 divisor = {divisor0} + {divisor1} * 2**128
529 quotient, remainder = divmod(dividend, divisor)
530 memory{quotient0} = quotient & 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
531 memory{quotient1} = quotient >> 128
532 memory{remainder0} = remainder & 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
533 memory{remainder1} = remainder >> 128
534 "}
535 }
536 CoreHint::Uint512DivModByUint256 {
537 dividend0,
538 dividend1,
539 dividend2,
540 dividend3,
541 divisor0,
542 divisor1,
543 quotient0,
544 quotient1,
545 quotient2,
546 quotient3,
547 remainder0,
548 remainder1,
549 } => {
550 let [dividend0, dividend1, dividend2, dividend3, divisor0, divisor1] =
551 [dividend0, dividend1, dividend2, dividend3, divisor0, divisor1]
552 .map(ResOperandAsIntegerFormatter);
553 formatdoc! {"
554
555 dividend = {dividend0} + {dividend1} * 2**128 + {dividend2} * 2**256 + \
556 {dividend3} * 2**384
557 divisor = {divisor0} + {divisor1} * 2**128
558 quotient, remainder = divmod(dividend, divisor)
559 memory{quotient0} = quotient & 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
560 memory{quotient1} = (quotient >> 128) & 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
561 memory{quotient2} = (quotient >> 256) & 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
562 memory{quotient3} = quotient >> 384
563 memory{remainder0} = remainder & 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
564 memory{remainder1} = remainder >> 128
565 "}
566 }
567 CoreHint::SquareRoot { value, dst } => {
568 let value = ResOperandAsIntegerFormatter(value);
569 formatdoc! {"
570
571 import math
572 memory{dst} = math.isqrt({value})
573 "}
574 }
575 CoreHint::Uint256SquareRoot {
576 value_low,
577 value_high,
578 sqrt0,
579 sqrt1,
580 remainder_low,
581 remainder_high,
582 sqrt_mul_2_minus_remainder_ge_u128,
583 } => {
584 let (value_low, value_high) = (
585 ResOperandAsIntegerFormatter(value_low),
586 ResOperandAsIntegerFormatter(value_high),
587 );
588 formatdoc! {"
589
590 import math;
591 value = {value_low} + {value_high} * 2**128
592 root = math.isqrt(value)
593 remainder = value - root ** 2
594 memory{sqrt0} = root & 0xFFFFFFFFFFFFFFFF
595 memory{sqrt1} = root >> 64
596 memory{remainder_low} = remainder & 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
597 memory{remainder_high} = remainder >> 128
598 memory{sqrt_mul_2_minus_remainder_ge_u128} = root * 2 - remainder >= 2**128
599 "}
600 }
601 CoreHint::LinearSplit { value, scalar, max_x, x, y } => {
602 let (value, scalar, max_x) = (
603 ResOperandAsIntegerFormatter(value),
604 ResOperandAsIntegerFormatter(scalar),
605 ResOperandAsIntegerFormatter(max_x),
606 );
607 formatdoc! {"
608
609 (value, scalar) = ({value}, {scalar})
610 x = min(value // scalar, {max_x})
611 y = value - x * scalar
612 memory{x} = x
613 memory{y} = y
614 "}
615 }
616 CoreHint::RandomEcPoint { x, y } => {
617 formatdoc! {"
618
619 from starkware.crypto.signature.signature import ALPHA, BETA, FIELD_PRIME
620 from starkware.python.math_utils import random_ec_point
621 (memory{x}, memory{y}) = random_ec_point(FIELD_PRIME, ALPHA, BETA)
622 "}
623 }
624 CoreHint::FieldSqrt { val, sqrt } => {
625 let val = ResOperandAsIntegerFormatter(val);
626 formatdoc! {"
627
628 from starkware.crypto.signature.signature import FIELD_PRIME
629 from starkware.python.math_utils import is_quad_residue, sqrt
630
631 val = {val}
632 if is_quad_residue(val, FIELD_PRIME):
633 memory{sqrt} = sqrt(val, FIELD_PRIME)
634 else:
635 memory{sqrt} = sqrt(val * 3, FIELD_PRIME)
636 "}
637 }
638 CoreHint::GetCurrentAccessIndex { range_check_ptr } => {
639 let rc = ResOperandAsAddressFormatter(range_check_ptr);
640 formatdoc! {"
641
642 current_access_indices = sorted(access_indices[key])[::-1]
643 current_access_index = current_access_indices.pop()
644 memory[{rc}] = current_access_index
645 "}
646 }
647 CoreHint::ShouldSkipSquashLoop { should_skip_loop } => {
648 format!("memory{should_skip_loop} = 0 if current_access_indices else 1")
649 }
650 CoreHint::GetCurrentAccessDelta { index_delta_minus1 } => formatdoc! {"
651
652 new_access_index = current_access_indices.pop()
653 memory{index_delta_minus1} = new_access_index - current_access_index - 1
654 current_access_index = new_access_index
655 "},
656 CoreHint::ShouldContinueSquashLoop { should_continue } => {
657 format!("memory{should_continue} = 1 if current_access_indices else 0")
658 }
659 CoreHint::GetNextDictKey { next_key } => formatdoc! {"
660 assert len(keys) > 0, 'No keys left but remaining_accesses > 0.'
661 memory{next_key} = key = keys.pop()
662 "},
663 CoreHint::GetSegmentArenaIndex { dict_end_ptr, dict_index } => {
664 let dict_end_ptr = ResOperandAsAddressFormatter(dict_end_ptr);
665 formatdoc! {"
666
667 memory{dict_index} = __segment_index_to_arena_index[
668 {dict_end_ptr}.segment_index
669 ]
670 "}
671 }
672 CoreHint::InitSquashData {
673 dict_accesses,
674 ptr_diff,
675 n_accesses,
676 big_keys,
677 first_key,
678 } => {
679 let (dict_accesses, ptr_diff, n_accesses) = (
680 ResOperandAsAddressFormatter(dict_accesses),
681 ResOperandAsIntegerFormatter(ptr_diff),
682 ResOperandAsIntegerFormatter(n_accesses),
683 );
684 formatdoc! {"
685
686 dict_access_size = 3
687 address = {dict_accesses}
688 assert {ptr_diff} % dict_access_size == 0, 'Accesses array size must be \
689 divisible by DictAccess.SIZE'
690 n_accesses = {n_accesses}
691 if '__squash_dict_max_size' in globals():
692 assert n_accesses <= __squash_dict_max_size, f'squash_dict() can only be \
693 used with n_accesses<={{__squash_dict_max_size}}. ' f'Got: \
694 n_accesses={{n_accesses}}.'
695 # A map from key to the list of indices accessing it.
696 access_indices = {{}}
697 for i in range(n_accesses):
698 key = memory[address + dict_access_size * i]
699 access_indices.setdefault(key, []).append(i)
700 # Descending list of keys.
701 keys = sorted(access_indices.keys(), reverse=True)
702 # Are the keys used bigger than range_check bound.
703 memory{big_keys} = 1 if keys[0] >= range_check_builtin.bound else 0
704 memory{first_key} = key = keys.pop()
705 "}
706 }
707 CoreHint::AssertLeFindSmallArcs { range_check_ptr, a, b } => {
708 let (range_check_ptr, a, b) = (
709 ResOperandAsAddressFormatter(range_check_ptr),
710 ResOperandAsIntegerFormatter(a),
711 ResOperandAsIntegerFormatter(b),
712 );
713 formatdoc! {"
714
715 import itertools
716
717 from starkware.cairo.common.math_utils import assert_integer
718 assert_integer({a})
719 assert_integer({b})
720 a = {a} % PRIME
721 b = {b} % PRIME
722 assert a <= b, f'a = {{a}} is not less than or equal to b = {{b}}.'
723
724 # Find an arc less than PRIME / 3, and another less than PRIME / 2.
725 lengths_and_indices = [(a, 0), (b - a, 1), (PRIME - 1 - b, 2)]
726 lengths_and_indices.sort()
727 assert lengths_and_indices[0][0] <= PRIME // 3 and lengths_and_indices[1][0] \
728 <= PRIME // 2
729 excluded = lengths_and_indices[2][1]
730
731 memory[{range_check_ptr} + 1], memory[{range_check_ptr} + 0] = (
732 divmod(lengths_and_indices[0][0], 3544607988759775765608368578435044694))
733 memory[{range_check_ptr} + 3], memory[{range_check_ptr} + 2] = (
734 divmod(lengths_and_indices[1][0], 5316911983139663648412552867652567041))
735 "}
736 }
737 CoreHint::AssertLeIsFirstArcExcluded { skip_exclude_a_flag } => {
738 format!("memory{skip_exclude_a_flag} = 1 if excluded != 0 else 0",)
739 }
740 CoreHint::AssertLeIsSecondArcExcluded { skip_exclude_b_minus_a } => {
741 format!("memory{skip_exclude_b_minus_a} = 1 if excluded != 1 else 0",)
742 }
743 CoreHint::DebugPrint { start, end } => {
744 let [start, end] = [start, end].map(ResOperandAsAddressFormatter);
745 formatdoc! {"
746
747 curr = {start}
748 end = {end}
749 while curr != end:
750 print(hex(memory[curr]))
751 curr += 1
752 "}
753 }
754 CoreHint::AllocConstantSize { size, dst } => {
755 let size = ResOperandAsIntegerFormatter(size);
756 formatdoc! {"
757
758 if '__boxed_segment' not in globals():
759 __boxed_segment = segments.add()
760 memory{dst} = __boxed_segment
761 __boxed_segment += {size}
762 "}
763 }
764 CoreHint::U256InvModN {
765 b0,
766 b1,
767 n0,
768 n1,
769 g0_or_no_inv,
770 g1_option,
771 s_or_r0,
772 s_or_r1,
773 t_or_k0,
774 t_or_k1,
775 } => {
776 let [b0, b1, n0, n1] = [b0, b1, n0, n1].map(ResOperandAsIntegerFormatter);
777 formatdoc! {"
778
779 from starkware.python.math_utils import igcdex
780
781 b = {b0} + ({b1} << 128)
782 n = {n0} + ({n1} << 128)
783
784 (_, r, g) = igcdex(n, b)
785 if n == 1:
786 memory{g0_or_no_inv} = 1
787 memory{g1_option} = 0
788 memory{s_or_r0} = {b0}
789 memory{s_or_r1} = {b1}
790 memory{t_or_k0} = 1
791 memory{t_or_k1} = 0
792 elif g != 1:
793 if g % 2 == 0:
794 g = 2
795 s = b // g
796 t = n // g
797 memory{g0_or_no_inv} = g & 0xffffffffffffffffffffffffffffffff
798 memory{g1_option} = g >> 128
799 memory{s_or_r0} = s & 0xffffffffffffffffffffffffffffffff
800 memory{s_or_r1} = s >> 128
801 memory{t_or_k0} = t & 0xffffffffffffffffffffffffffffffff
802 memory{t_or_k1} = t >> 128
803 else:
804 r %= n
805 k = (r * b - 1) // n
806 memory{g0_or_no_inv} = 0
807 memory{s_or_r0} = r & 0xffffffffffffffffffffffffffffffff
808 memory{s_or_r1} = r >> 128
809 memory{t_or_k0} = k & 0xffffffffffffffffffffffffffffffff
810 memory{t_or_k1} = k >> 128
811 "}
812 }
813 CoreHint::EvalCircuit { n_add_mods, add_mod_builtin, n_mul_mods, mul_mod_builtin } => {
814 let n_add_mods = ResOperandAsIntegerFormatter(n_add_mods);
815 let add_mod_builtin = ResOperandAsAddressFormatter(add_mod_builtin);
816 let n_mul_mods = ResOperandAsIntegerFormatter(n_mul_mods);
817 let mul_mod_builtin = ResOperandAsAddressFormatter(mul_mod_builtin);
818 formatdoc! {"
819
820 from starkware.cairo.lang.builtins.modulo.mod_builtin_runner import ModBuiltinRunner
821
822 ModBuiltinRunner.fill_memory(
823 memory=memory,
824 add_mod=({add_mod_builtin}, builtin_runners[\"add_mod_builtin\"], {n_add_mods}),
825 mul_mod=({mul_mod_builtin}, builtin_runners[\"mul_mod_builtin\"], {n_mul_mods}),
826 )
827 "}
828 }
829 }
830 }
831}
832
833impl PythonicHint for StarknetHint {
834 fn get_pythonic_hint(&self) -> String {
835 match self {
836 StarknetHint::SystemCall { system } => {
837 format!(
838 "syscall_handler.syscall(syscall_ptr={})",
839 ResOperandAsAddressFormatter(system)
840 )
841 }
842 StarknetHint::Cheatcode { .. } => {
843 r#"raise NotImplementedError("Cheatcode")"#.to_string()
844 }
845 }
846 }
847}
848
849impl PythonicHint for ExternalHint {
850 fn get_pythonic_hint(&self) -> String {
851 match self {
852 Self::AddRelocationRule { src, dst } => {
853 let [src, dst] = [src, dst].map(ResOperandAsAddressFormatter);
854 format!("memory.add_relocation_rule(src_ptr={src}, dest_ptr={dst})")
855 }
856 Self::WriteRunParam { index, dst } => {
857 let index = ResOperandAsIntegerFormatter(index);
858 format!("WriteRunParam {{ dst: {dst}, index: {index} }}",)
859 }
860 Self::AddMarker { start, end } => {
861 let [start, end] = [start, end].map(ResOperandAsAddressFormatter);
862 format!("AddMarker {{ start: {start}, end: {end} }}")
863 }
864 Self::AddTrace { flag } => {
865 format!("AddTrace {{ flag: {} }}", ResOperandAsIntegerFormatter(flag))
866 }
867 }
868 }
869}