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 #[cfg_attr(feature = "parity-scale-codec", codec(index = 4))]
367 Blake2sCompress {
368 state: ResOperand,
369 byte_count: ResOperand,
370 message: ResOperand,
371 output: ResOperand,
372 finalize: ResOperand,
373 },
374}
375
376struct DerefOrImmediateFormatter<'a>(&'a DerefOrImmediate);
377impl Display for DerefOrImmediateFormatter<'_> {
378 fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
379 match self.0 {
380 DerefOrImmediate::Deref(d) => write!(f, "memory{d}"),
381 DerefOrImmediate::Immediate(i) => write!(f, "{}", i.value),
382 }
383 }
384}
385
386struct ResOperandAsIntegerFormatter<'a>(&'a ResOperand);
387impl Display for ResOperandAsIntegerFormatter<'_> {
388 fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
389 match self.0 {
390 ResOperand::Deref(d) => write!(f, "memory{d}"),
391 ResOperand::DoubleDeref(d, i) => write!(f, "memory[memory{d} + {i}]"),
392 ResOperand::Immediate(i) => write!(f, "{}", i.value),
393 ResOperand::BinOp(bin_op) => {
394 write!(
395 f,
396 "(memory{} {} {}) % PRIME",
397 bin_op.a,
398 bin_op.op,
399 DerefOrImmediateFormatter(&bin_op.b)
400 )
401 }
402 }
403 }
404}
405
406struct ResOperandAsAddressFormatter<'a>(&'a ResOperand);
407impl Display for ResOperandAsAddressFormatter<'_> {
408 fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
409 match self.0 {
410 ResOperand::Deref(d) => write!(f, "memory{d}"),
411 ResOperand::DoubleDeref(d, i) => write!(f, "memory[memory{d} + {i}]"),
412 ResOperand::Immediate(i) => {
413 unreachable!("Address cannot be an immediate: {}.", i.value)
414 }
415 ResOperand::BinOp(bin_op) => {
416 write!(
417 f,
418 "memory{} {} {}",
419 bin_op.a,
420 bin_op.op,
421 DerefOrImmediateFormatter(&bin_op.b)
422 )
423 }
424 }
425 }
426}
427
428impl PythonicHint for CoreHintBase {
429 fn get_pythonic_hint(&self) -> String {
430 match self {
431 CoreHintBase::Core(hint) => hint.get_pythonic_hint(),
432 CoreHintBase::Deprecated(_) => {
433 unreachable!("Deprecated hints do not have a pythonic version.")
434 }
435 }
436 }
437}
438
439impl PythonicHint for CoreHint {
440 fn get_pythonic_hint(&self) -> String {
441 match self {
442 CoreHint::AllocSegment { dst } => format!("memory{dst} = segments.add()"),
443 CoreHint::AllocFelt252Dict { segment_arena_ptr } => {
444 let segment_arena_ptr = ResOperandAsAddressFormatter(segment_arena_ptr);
445 formatdoc! {"
446
447 if '__dict_manager' not in globals():
448 from starkware.cairo.common.dict import DictManager
449 __dict_manager = DictManager()
450
451 if '__segment_index_to_arena_index' not in globals():
452 # A map from the relocatable value segment index to the index in the
453 # arena.
454 __segment_index_to_arena_index = {{}}
455
456 # {segment_arena_ptr} is the address of the next SegmentArenaBuiltin.
457 # memory[{segment_arena_ptr} - 2] is the number of allocated segments.
458 index = memory[{segment_arena_ptr} - 2]
459
460 segment_start = __dict_manager.new_default_dict(
461 segments, 0, temp_segment=index > 0
462 )
463
464 # Update '__segment_index_to_arena_index'.
465 __segment_index_to_arena_index[segment_start.segment_index] = index
466
467 # Update 'SegmentInfo::start'.
468 # memory[{segment_arena_ptr} - 3] is the address of the segment arena infos
469 # segment. index * 3 is added to get the address of the new SegmentInfo.
470 memory[memory[{segment_arena_ptr} - 3] + index * 3] = segment_start
471 "}
472 }
473 CoreHint::Felt252DictEntryInit { dict_ptr, key } => {
474 let (dict_ptr, key) =
475 (ResOperandAsAddressFormatter(dict_ptr), ResOperandAsIntegerFormatter(key));
476 formatdoc! {"
477
478 dict_tracker = __dict_manager.get_tracker({dict_ptr})
479 dict_tracker.current_ptr += 3
480 memory[{dict_ptr} + 1] = dict_tracker.data[{key}]
481 "}
482 }
483 CoreHint::Felt252DictEntryUpdate { dict_ptr, value } => {
484 let (dict_ptr, value) =
485 (ResOperandAsAddressFormatter(dict_ptr), ResOperandAsIntegerFormatter(value));
486 formatdoc! {"
487
488 dict_tracker = __dict_manager.get_tracker({dict_ptr})
489 dict_tracker.data[memory[{dict_ptr} - 3]] = {value}
490 "}
491 }
492 CoreHint::TestLessThan { lhs, rhs, dst } => {
493 format!(
494 "memory{dst} = {} < {}",
495 ResOperandAsIntegerFormatter(lhs),
496 ResOperandAsIntegerFormatter(rhs)
497 )
498 }
499 CoreHint::TestLessThanOrEqual { lhs, rhs, dst } => format!(
500 "memory{dst} = {} <= {}",
501 ResOperandAsIntegerFormatter(lhs),
502 ResOperandAsIntegerFormatter(rhs)
503 ),
504 CoreHint::TestLessThanOrEqualAddress { lhs, rhs, dst } => format!(
505 "memory{dst} = {} <= {}",
506 ResOperandAsAddressFormatter(lhs),
507 ResOperandAsAddressFormatter(rhs)
508 ),
509 CoreHint::WideMul128 { lhs, rhs, high, low } => format!(
510 "(memory{high}, memory{low}) = divmod({} * {}, 2**128)",
511 ResOperandAsIntegerFormatter(lhs),
512 ResOperandAsIntegerFormatter(rhs)
513 ),
514 CoreHint::DivMod { lhs, rhs, quotient, remainder } => format!(
515 "(memory{quotient}, memory{remainder}) = divmod({}, {})",
516 ResOperandAsIntegerFormatter(lhs),
517 ResOperandAsIntegerFormatter(rhs)
518 ),
519 CoreHint::Uint256DivMod {
520 dividend0,
521 dividend1,
522 quotient0,
523 quotient1,
524 divisor0,
525 divisor1,
526 remainder0,
527 remainder1,
528 } => {
529 let (dividend0, dividend1, divisor0, divisor1) = (
530 ResOperandAsIntegerFormatter(dividend0),
531 ResOperandAsIntegerFormatter(dividend1),
532 ResOperandAsIntegerFormatter(divisor0),
533 ResOperandAsIntegerFormatter(divisor1),
534 );
535 formatdoc! {"
536
537 dividend = {dividend0} + {dividend1} * 2**128
538 divisor = {divisor0} + {divisor1} * 2**128
539 quotient, remainder = divmod(dividend, divisor)
540 memory{quotient0} = quotient & 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
541 memory{quotient1} = quotient >> 128
542 memory{remainder0} = remainder & 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
543 memory{remainder1} = remainder >> 128
544 "}
545 }
546 CoreHint::Uint512DivModByUint256 {
547 dividend0,
548 dividend1,
549 dividend2,
550 dividend3,
551 divisor0,
552 divisor1,
553 quotient0,
554 quotient1,
555 quotient2,
556 quotient3,
557 remainder0,
558 remainder1,
559 } => {
560 let [dividend0, dividend1, dividend2, dividend3, divisor0, divisor1] =
561 [dividend0, dividend1, dividend2, dividend3, divisor0, divisor1]
562 .map(ResOperandAsIntegerFormatter);
563 formatdoc! {"
564
565 dividend = {dividend0} + {dividend1} * 2**128 + {dividend2} * 2**256 + \
566 {dividend3} * 2**384
567 divisor = {divisor0} + {divisor1} * 2**128
568 quotient, remainder = divmod(dividend, divisor)
569 memory{quotient0} = quotient & 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
570 memory{quotient1} = (quotient >> 128) & 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
571 memory{quotient2} = (quotient >> 256) & 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
572 memory{quotient3} = quotient >> 384
573 memory{remainder0} = remainder & 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
574 memory{remainder1} = remainder >> 128
575 "}
576 }
577 CoreHint::SquareRoot { value, dst } => {
578 let value = ResOperandAsIntegerFormatter(value);
579 formatdoc! {"
580
581 import math
582 memory{dst} = math.isqrt({value})
583 "}
584 }
585 CoreHint::Uint256SquareRoot {
586 value_low,
587 value_high,
588 sqrt0,
589 sqrt1,
590 remainder_low,
591 remainder_high,
592 sqrt_mul_2_minus_remainder_ge_u128,
593 } => {
594 let (value_low, value_high) = (
595 ResOperandAsIntegerFormatter(value_low),
596 ResOperandAsIntegerFormatter(value_high),
597 );
598 formatdoc! {"
599
600 import math;
601 value = {value_low} + {value_high} * 2**128
602 root = math.isqrt(value)
603 remainder = value - root ** 2
604 memory{sqrt0} = root & 0xFFFFFFFFFFFFFFFF
605 memory{sqrt1} = root >> 64
606 memory{remainder_low} = remainder & 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
607 memory{remainder_high} = remainder >> 128
608 memory{sqrt_mul_2_minus_remainder_ge_u128} = root * 2 - remainder >= 2**128
609 "}
610 }
611 CoreHint::LinearSplit { value, scalar, max_x, x, y } => {
612 let (value, scalar, max_x) = (
613 ResOperandAsIntegerFormatter(value),
614 ResOperandAsIntegerFormatter(scalar),
615 ResOperandAsIntegerFormatter(max_x),
616 );
617 formatdoc! {"
618
619 (value, scalar) = ({value}, {scalar})
620 x = min(value // scalar, {max_x})
621 y = value - x * scalar
622 memory{x} = x
623 memory{y} = y
624 "}
625 }
626 CoreHint::RandomEcPoint { x, y } => {
627 formatdoc! {"
628
629 from starkware.crypto.signature.signature import ALPHA, BETA, FIELD_PRIME
630 from starkware.python.math_utils import random_ec_point
631 (memory{x}, memory{y}) = random_ec_point(FIELD_PRIME, ALPHA, BETA)
632 "}
633 }
634 CoreHint::FieldSqrt { val, sqrt } => {
635 let val = ResOperandAsIntegerFormatter(val);
636 formatdoc! {"
637
638 from starkware.crypto.signature.signature import FIELD_PRIME
639 from starkware.python.math_utils import is_quad_residue, sqrt
640
641 val = {val}
642 if is_quad_residue(val, FIELD_PRIME):
643 memory{sqrt} = sqrt(val, FIELD_PRIME)
644 else:
645 memory{sqrt} = sqrt(val * 3, FIELD_PRIME)
646 "}
647 }
648 CoreHint::GetCurrentAccessIndex { range_check_ptr } => {
649 let rc = ResOperandAsAddressFormatter(range_check_ptr);
650 formatdoc! {"
651
652 current_access_indices = sorted(access_indices[key])[::-1]
653 current_access_index = current_access_indices.pop()
654 memory[{rc}] = current_access_index
655 "}
656 }
657 CoreHint::ShouldSkipSquashLoop { should_skip_loop } => {
658 format!("memory{should_skip_loop} = 0 if current_access_indices else 1")
659 }
660 CoreHint::GetCurrentAccessDelta { index_delta_minus1 } => formatdoc! {"
661
662 new_access_index = current_access_indices.pop()
663 memory{index_delta_minus1} = new_access_index - current_access_index - 1
664 current_access_index = new_access_index
665 "},
666 CoreHint::ShouldContinueSquashLoop { should_continue } => {
667 format!("memory{should_continue} = 1 if current_access_indices else 0")
668 }
669 CoreHint::GetNextDictKey { next_key } => formatdoc! {"
670 assert len(keys) > 0, 'No keys left but remaining_accesses > 0.'
671 memory{next_key} = key = keys.pop()
672 "},
673 CoreHint::GetSegmentArenaIndex { dict_end_ptr, dict_index } => {
674 let dict_end_ptr = ResOperandAsAddressFormatter(dict_end_ptr);
675 formatdoc! {"
676
677 memory{dict_index} = __segment_index_to_arena_index[
678 {dict_end_ptr}.segment_index
679 ]
680 "}
681 }
682 CoreHint::InitSquashData {
683 dict_accesses,
684 ptr_diff,
685 n_accesses,
686 big_keys,
687 first_key,
688 } => {
689 let (dict_accesses, ptr_diff, n_accesses) = (
690 ResOperandAsAddressFormatter(dict_accesses),
691 ResOperandAsIntegerFormatter(ptr_diff),
692 ResOperandAsIntegerFormatter(n_accesses),
693 );
694 formatdoc! {"
695
696 dict_access_size = 3
697 address = {dict_accesses}
698 assert {ptr_diff} % dict_access_size == 0, 'Accesses array size must be \
699 divisible by DictAccess.SIZE'
700 n_accesses = {n_accesses}
701 if '__squash_dict_max_size' in globals():
702 assert n_accesses <= __squash_dict_max_size, f'squash_dict() can only be \
703 used with n_accesses<={{__squash_dict_max_size}}. ' f'Got: \
704 n_accesses={{n_accesses}}.'
705 # A map from key to the list of indices accessing it.
706 access_indices = {{}}
707 for i in range(n_accesses):
708 key = memory[address + dict_access_size * i]
709 access_indices.setdefault(key, []).append(i)
710 # Descending list of keys.
711 keys = sorted(access_indices.keys(), reverse=True)
712 # Are the keys used bigger than range_check bound.
713 memory{big_keys} = 1 if keys[0] >= range_check_builtin.bound else 0
714 memory{first_key} = key = keys.pop()
715 "}
716 }
717 CoreHint::AssertLeFindSmallArcs { range_check_ptr, a, b } => {
718 let (range_check_ptr, a, b) = (
719 ResOperandAsAddressFormatter(range_check_ptr),
720 ResOperandAsIntegerFormatter(a),
721 ResOperandAsIntegerFormatter(b),
722 );
723 formatdoc! {"
724
725 import itertools
726
727 from starkware.cairo.common.math_utils import assert_integer
728 assert_integer({a})
729 assert_integer({b})
730 a = {a} % PRIME
731 b = {b} % PRIME
732 assert a <= b, f'a = {{a}} is not less than or equal to b = {{b}}.'
733
734 # Find an arc less than PRIME / 3, and another less than PRIME / 2.
735 lengths_and_indices = [(a, 0), (b - a, 1), (PRIME - 1 - b, 2)]
736 lengths_and_indices.sort()
737 assert lengths_and_indices[0][0] <= PRIME // 3 and lengths_and_indices[1][0] \
738 <= PRIME // 2
739 excluded = lengths_and_indices[2][1]
740
741 memory[{range_check_ptr} + 1], memory[{range_check_ptr} + 0] = (
742 divmod(lengths_and_indices[0][0], 3544607988759775765608368578435044694))
743 memory[{range_check_ptr} + 3], memory[{range_check_ptr} + 2] = (
744 divmod(lengths_and_indices[1][0], 5316911983139663648412552867652567041))
745 "}
746 }
747 CoreHint::AssertLeIsFirstArcExcluded { skip_exclude_a_flag } => {
748 format!("memory{skip_exclude_a_flag} = 1 if excluded != 0 else 0",)
749 }
750 CoreHint::AssertLeIsSecondArcExcluded { skip_exclude_b_minus_a } => {
751 format!("memory{skip_exclude_b_minus_a} = 1 if excluded != 1 else 0",)
752 }
753 CoreHint::DebugPrint { start, end } => {
754 let [start, end] = [start, end].map(ResOperandAsAddressFormatter);
755 formatdoc! {"
756
757 curr = {start}
758 end = {end}
759 while curr != end:
760 print(hex(memory[curr]))
761 curr += 1
762 "}
763 }
764 CoreHint::AllocConstantSize { size, dst } => {
765 let size = ResOperandAsIntegerFormatter(size);
766 formatdoc! {"
767
768 if '__boxed_segment' not in globals():
769 __boxed_segment = segments.add()
770 memory{dst} = __boxed_segment
771 __boxed_segment += {size}
772 "}
773 }
774 CoreHint::U256InvModN {
775 b0,
776 b1,
777 n0,
778 n1,
779 g0_or_no_inv,
780 g1_option,
781 s_or_r0,
782 s_or_r1,
783 t_or_k0,
784 t_or_k1,
785 } => {
786 let [b0, b1, n0, n1] = [b0, b1, n0, n1].map(ResOperandAsIntegerFormatter);
787 formatdoc! {"
788
789 from starkware.python.math_utils import igcdex
790
791 b = {b0} + ({b1} << 128)
792 n = {n0} + ({n1} << 128)
793
794 (_, r, g) = igcdex(n, b)
795 if n == 1:
796 memory{g0_or_no_inv} = 1
797 memory{g1_option} = 0
798 memory{s_or_r0} = {b0}
799 memory{s_or_r1} = {b1}
800 memory{t_or_k0} = 1
801 memory{t_or_k1} = 0
802 elif g != 1:
803 if g % 2 == 0:
804 g = 2
805 s = b // g
806 t = n // g
807 memory{g0_or_no_inv} = g & 0xffffffffffffffffffffffffffffffff
808 memory{g1_option} = g >> 128
809 memory{s_or_r0} = s & 0xffffffffffffffffffffffffffffffff
810 memory{s_or_r1} = s >> 128
811 memory{t_or_k0} = t & 0xffffffffffffffffffffffffffffffff
812 memory{t_or_k1} = t >> 128
813 else:
814 r %= n
815 k = (r * b - 1) // n
816 memory{g0_or_no_inv} = 0
817 memory{s_or_r0} = r & 0xffffffffffffffffffffffffffffffff
818 memory{s_or_r1} = r >> 128
819 memory{t_or_k0} = k & 0xffffffffffffffffffffffffffffffff
820 memory{t_or_k1} = k >> 128
821 "}
822 }
823 CoreHint::EvalCircuit { n_add_mods, add_mod_builtin, n_mul_mods, mul_mod_builtin } => {
824 let n_add_mods = ResOperandAsIntegerFormatter(n_add_mods);
825 let add_mod_builtin = ResOperandAsAddressFormatter(add_mod_builtin);
826 let n_mul_mods = ResOperandAsIntegerFormatter(n_mul_mods);
827 let mul_mod_builtin = ResOperandAsAddressFormatter(mul_mod_builtin);
828 formatdoc! {"
829
830 from starkware.cairo.lang.builtins.modulo.mod_builtin_runner import ModBuiltinRunner
831
832 ModBuiltinRunner.fill_memory(
833 memory=memory,
834 add_mod=({add_mod_builtin}, builtin_runners[\"add_mod_builtin\"], {n_add_mods}),
835 mul_mod=({mul_mod_builtin}, builtin_runners[\"mul_mod_builtin\"], {n_mul_mods}),
836 )
837 "}
838 }
839 }
840 }
841}
842
843impl PythonicHint for StarknetHint {
844 fn get_pythonic_hint(&self) -> String {
845 match self {
846 StarknetHint::SystemCall { system } => {
847 format!(
848 "syscall_handler.syscall(syscall_ptr={})",
849 ResOperandAsAddressFormatter(system)
850 )
851 }
852 StarknetHint::Cheatcode { .. } => {
853 r#"raise NotImplementedError("Cheatcode")"#.to_string()
854 }
855 }
856 }
857}
858
859impl PythonicHint for ExternalHint {
860 fn get_pythonic_hint(&self) -> String {
861 match self {
862 Self::AddRelocationRule { src, dst } => {
863 let [src, dst] = [src, dst].map(ResOperandAsAddressFormatter);
864 format!("memory.add_relocation_rule(src_ptr={src}, dest_ptr={dst})")
865 }
866 Self::WriteRunParam { index, dst } => {
867 let index = ResOperandAsIntegerFormatter(index);
868 format!("WriteRunParam {{ dst: {dst}, index: {index} }}",)
869 }
870 Self::AddMarker { start, end } => {
871 let [start, end] = [start, end].map(ResOperandAsAddressFormatter);
872 format!("AddMarker {{ start: {start}, end: {end} }}")
873 }
874 Self::Blake2sCompress { state, byte_count, message, output, finalize } => {
875 let [state, byte_count, message, output] =
876 [state, byte_count, message, output].map(ResOperandAsAddressFormatter);
877 let finalize = ResOperandAsIntegerFormatter(finalize);
878 formatdoc! {"
879
880 Blake2sCompress {{
881 state: {state},
882 byte_count: {byte_count},
883 message: {message},
884 output: {output},
885 finalize: {finalize}
886 }}
887 "}
888 }
889 Self::AddTrace { flag } => {
890 format!("AddTrace {{ flag: {} }}", ResOperandAsIntegerFormatter(flag))
891 }
892 }
893 }
894}