1use super::*;
17
18impl<N: Network> StackTrait<N> for Stack<N> {
19 fn matches_value_type(&self, value: &Value<N>, value_type: &ValueType<N>) -> Result<()> {
21 match (value, value_type) {
23 (Value::Plaintext(plaintext), ValueType::Constant(plaintext_type))
24 | (Value::Plaintext(plaintext), ValueType::Public(plaintext_type))
25 | (Value::Plaintext(plaintext), ValueType::Private(plaintext_type)) => {
26 self.matches_plaintext(plaintext, plaintext_type)
27 }
28 (Value::Record(record), ValueType::Record(record_name)) => self.matches_record(record, record_name),
29 (Value::Record(record), ValueType::ExternalRecord(locator)) => {
30 self.matches_external_record(record, locator)
31 }
32 (Value::Future(future), ValueType::Future(locator)) => self.matches_future(future, locator),
33 (Value::DynamicRecord(_), ValueType::DynamicRecord) => Ok(()),
34 (Value::DynamicFuture(_), ValueType::DynamicFuture) => Ok(()),
35 (value, _) => bail!("A value '{value}' does not match its declared value type '{value_type}'"),
36 }
37 }
38
39 fn matches_register_type(&self, stack_value: &Value<N>, register_type: &RegisterType<N>) -> Result<()> {
41 match (stack_value, register_type) {
42 (Value::Plaintext(plaintext), RegisterType::Plaintext(plaintext_type)) => {
43 self.matches_plaintext(plaintext, plaintext_type)
44 }
45 (Value::Record(record), RegisterType::Record(record_name)) => self.matches_record(record, record_name),
46 (Value::Record(record), RegisterType::ExternalRecord(locator)) => {
47 self.matches_external_record(record, locator)
48 }
49 (Value::Future(future), RegisterType::Future(locator)) => self.matches_future(future, locator),
50 (Value::DynamicRecord(_), RegisterType::DynamicRecord) => Ok(()),
51 (Value::DynamicFuture(_), RegisterType::DynamicFuture) => Ok(()),
52 (value, _) => bail!("A value '{value}' does not match its declared register type '{register_type}'"),
53 }
54 }
55
56 fn matches_external_record(&self, record: &Record<N, Plaintext<N>>, locator: &Locator<N>) -> Result<()> {
58 let record_name = locator.resource();
60
61 ensure!(!Program::is_reserved_keyword(record_name), "Record name '{record_name}' is reserved");
63
64 let external_stack = self.get_external_stack(locator.program_id())?;
66 let Ok(record_type) = external_stack.program().get_record(locator.resource()) else {
68 bail!("External '{locator}' is not defined in the program")
69 };
70
71 if record_type.name() != record_name {
73 bail!("Expected external record '{record_name}', found external record '{}'", record_type.name())
74 }
75
76 external_stack.matches_record_internal(record, record_type, 0)
77 }
78
79 fn matches_record(&self, record: &Record<N, Plaintext<N>>, record_name: &Identifier<N>) -> Result<()> {
81 ensure!(!Program::is_reserved_keyword(record_name), "Record name '{record_name}' is reserved");
83
84 let Ok(record_type) = self.program().get_record(record_name) else {
86 bail!("Record '{record_name}' is not defined in the program")
87 };
88
89 if record_type.name() != record_name {
91 bail!("Expected record '{record_name}', found record '{}'", record_type.name())
92 }
93
94 self.matches_record_internal(record, record_type, 0)
95 }
96
97 fn matches_plaintext(&self, plaintext: &Plaintext<N>, plaintext_type: &PlaintextType<N>) -> Result<()> {
99 self.matches_plaintext_internal(plaintext, plaintext_type, 0)
100 }
101
102 fn matches_future(&self, future: &Future<N>, locator: &Locator<N>) -> Result<()> {
104 self.matches_future_internal(future, locator, 0)
105 }
106
107 fn contains_proving_key(&self, function_or_record_name: &Identifier<N>) -> bool {
110 self.proving_keys.read().contains_key(function_or_record_name)
111 }
112
113 fn get_proving_key(&self, function_or_record_name: &Identifier<N>) -> Result<ProvingKey<N>> {
116 self.try_insert_credits_function_proving_key(function_or_record_name)?;
118 match self.proving_keys.read().get(function_or_record_name) {
120 Some(pk) => Ok(pk.clone()),
121 None => bail!("Proving key not found for: {}/{}", self.program.id(), function_or_record_name),
122 }
123 }
124
125 fn insert_proving_key(&self, function_or_record_name: &Identifier<N>, proving_key: ProvingKey<N>) -> Result<()> {
128 ensure!(
130 self.program.contains_function(function_or_record_name)
131 || self.program.contains_record(function_or_record_name),
132 "'{function_or_record_name}' does not exist as a function or record in program '{}'.",
133 self.program.id()
134 );
135 self.proving_keys.write().insert(*function_or_record_name, proving_key);
137 Ok(())
138 }
139
140 fn remove_proving_key(&self, function_or_record_name: &Identifier<N>) {
143 self.proving_keys.write().shift_remove(function_or_record_name);
144 }
145
146 fn contains_verifying_key(&self, function_or_record_name: &Identifier<N>) -> bool {
149 self.verifying_keys.read().contains_key(function_or_record_name)
150 }
151
152 fn get_verifying_key(&self, function_or_record_name: &Identifier<N>) -> Result<VerifyingKey<N>> {
155 match self.verifying_keys.read().get(function_or_record_name) {
157 Some(vk) => Ok(vk.clone()),
158 None => bail!("Verifying key not found for: {}/{}", self.program.id(), function_or_record_name),
159 }
160 }
161
162 fn insert_verifying_key(
165 &self,
166 function_or_record_name: &Identifier<N>,
167 verifying_key: VerifyingKey<N>,
168 ) -> Result<()> {
169 ensure!(
171 self.program.contains_function(function_or_record_name)
172 || self.program.contains_record(function_or_record_name),
173 "'{function_or_record_name}' does not exist as a function or record in program '{}'.",
174 self.program.id()
175 );
176 self.verifying_keys.write().insert(*function_or_record_name, verifying_key);
178 Ok(())
179 }
180
181 fn remove_verifying_key(&self, function_or_record_name: &Identifier<N>) {
184 self.verifying_keys.write().shift_remove(function_or_record_name);
185 }
186
187 fn program(&self) -> &Program<N> {
189 &self.program
190 }
191
192 fn program_id(&self) -> &ProgramID<N> {
194 self.program.id()
195 }
196
197 fn program_address(&self) -> &Address<N> {
199 &self.program_address
200 }
201
202 fn program_checksum(&self) -> &[U8<N>; 32] {
204 &self.program_checksum
205 }
206
207 #[inline]
209 fn program_checksum_as_field(&self) -> Result<Field<N>> {
210 let bits = self
212 .program_checksum
213 .iter()
214 .flat_map(|byte| byte.to_bits_le())
215 .take(Field::<N>::SIZE_IN_DATA_BITS)
216 .collect::<Vec<_>>();
217 Field::from_bits_le(&bits)
219 }
220
221 #[inline]
223 fn program_edition(&self) -> U16<N> {
224 self.program_edition
225 }
226
227 #[inline]
229 fn program_amendment_count(&self) -> u64 {
230 self.program_amendment_count
231 }
232
233 fn set_program_amendment_count(&mut self, program_amendment_count: u64) {
235 self.program_amendment_count = program_amendment_count;
236 }
237
238 #[inline]
240 fn program_owner(&self) -> &Option<Address<N>> {
241 &self.program_owner
242 }
243
244 fn set_program_owner(&mut self, program_owner: Option<Address<N>>) {
247 self.program_owner = program_owner;
248 }
249
250 fn get_external_stack(&self, program_id: &ProgramID<N>) -> Result<Arc<Stack<N>>> {
255 ensure!(
257 program_id != self.program.id(),
258 "Attempted to get the main program '{program_id}' as an external program."
259 );
260 ensure!(self.program.contains_import(program_id), "External program '{program_id}' is not imported.");
262 self.stacks
264 .upgrade()
265 .ok_or_else(|| anyhow!("Process-level stack map does not exist"))?
266 .read()
267 .get(program_id)
268 .cloned()
269 .ok_or_else(|| anyhow!("External stack for '{program_id}' does not exist"))
270 }
271
272 fn get_stack_global(&self, program_id: &ProgramID<N>) -> Result<Arc<Stack<N>>> {
277 self.stacks
279 .upgrade()
280 .ok_or_else(|| anyhow!("Process-level stack map does not exist"))?
281 .read()
282 .get(program_id)
283 .cloned()
284 .ok_or_else(|| anyhow!("External stack for '{program_id}' does not exist"))
285 }
286
287 fn get_function(&self, function_name: &Identifier<N>) -> Result<Function<N>> {
289 self.program.get_function(function_name)
290 }
291
292 fn get_function_ref(&self, function_name: &Identifier<N>) -> Result<&Function<N>> {
294 self.program.get_function_ref(function_name)
295 }
296
297 fn get_minimum_number_of_calls(&self, function_name: &Identifier<N>) -> Result<usize> {
300 let mut num_calls = 1;
302 let mut queue = vec![(StackRef::Internal(self), *function_name)];
304 while let Some((stack_ref, function_name)) = queue.pop() {
306 ensure!(
309 num_calls < Transaction::<N>::MAX_TRANSITIONS,
310 "Number of calls must be less than '{}'",
311 Transaction::<N>::MAX_TRANSITIONS
312 );
313 for instruction in stack_ref.get_function_ref(&function_name)?.instructions() {
315 match instruction {
316 Instruction::Call(call) => {
317 if call.is_function_call(&*stack_ref)? {
319 num_calls += 1;
321 match call.operator() {
323 CallOperator::Locator(locator) => {
324 let stack = if locator.program_id() == self.program().id() {
327 StackRef::Internal(self)
328 } else {
329 StackRef::External(stack_ref.get_external_stack(locator.program_id())?)
330 };
331 queue.push((stack, *locator.resource()));
332 }
333 CallOperator::Resource(resource) => {
334 queue.push((stack_ref.clone(), *resource));
335 }
336 }
337 }
338 }
339 Instruction::CallDynamic(_) => {
340 num_calls += 1
342 }
343 _ => (),
344 }
345 }
346 }
347 Ok(num_calls)
349 }
350
351 fn contains_dynamic_call(&self, function_name: &Identifier<N>) -> Result<bool> {
353 let mut num_calls = 1;
355 let mut queue = vec![(StackRef::Internal(self), *function_name)];
357 while let Some((stack_ref, function_name)) = queue.pop() {
359 ensure!(
362 num_calls < Transaction::<N>::MAX_TRANSITIONS,
363 "Number of calls must be less than '{}'",
364 Transaction::<N>::MAX_TRANSITIONS
365 );
366 for instruction in stack_ref.get_function_ref(&function_name)?.instructions() {
368 match instruction {
369 Instruction::Call(call) => {
370 if call.is_function_call(&*stack_ref)? {
372 num_calls += 1;
374 match call.operator() {
376 CallOperator::Locator(locator) => {
377 let stack = if locator.program_id() == self.program().id() {
380 StackRef::Internal(self)
381 } else {
382 StackRef::External(stack_ref.get_external_stack(locator.program_id())?)
383 };
384 queue.push((stack, *locator.resource()));
385 }
386 CallOperator::Resource(resource) => {
387 queue.push((stack_ref.clone(), *resource));
388 }
389 }
390 }
391 }
392 Instruction::CallDynamic(_) => return Ok(true),
393 _ => (),
394 }
395 }
396 }
397 Ok(false)
399 }
400
401 fn sample_value<R: Rng + CryptoRng>(
403 &self,
404 burner_address: &Address<N>,
405 register_type: &RegisterType<N>,
406 rng: &mut R,
407 ) -> Result<Value<N>> {
408 match register_type {
409 RegisterType::Plaintext(plaintext_type) => {
410 Ok(Value::Plaintext(self.sample_plaintext(plaintext_type, rng)?))
411 }
412 RegisterType::Record(record_name) => {
413 Ok(Value::Record(self.sample_record(burner_address, record_name, Group::rand(rng), rng)?))
414 }
415 RegisterType::ExternalRecord(locator) => {
416 let stack = self.get_external_stack(locator.program_id())?;
418 Ok(Value::Record(stack.sample_record(burner_address, locator.resource(), Group::rand(rng), rng)?))
420 }
421 RegisterType::Future(locator) => Ok(Value::Future(self.sample_future(locator, rng)?)),
422 RegisterType::DynamicRecord => Ok(Value::DynamicRecord(self.sample_dynamic_record(rng)?)),
423 RegisterType::DynamicFuture => Ok(Value::DynamicFuture(self.sample_dynamic_future(rng)?)),
424 }
425 }
426
427 fn sample_record<R: Rng + CryptoRng>(
429 &self,
430 burner_address: &Address<N>,
431 record_name: &Identifier<N>,
432 nonce: Group<N>,
433 rng: &mut R,
434 ) -> Result<Record<N, Plaintext<N>>> {
435 let record = self.sample_record_internal(burner_address, record_name, nonce, 0, rng)?;
437 self.matches_record(&record, record_name)?;
439 Ok(record)
441 }
442
443 fn sample_record_using_tvk<R: Rng + CryptoRng>(
445 &self,
446 burner_address: &Address<N>,
447 record_name: &Identifier<N>,
448 tvk: Field<N>,
449 index: Field<N>,
450 rng: &mut R,
451 ) -> Result<Record<N, Plaintext<N>>> {
452 let randomizer = N::hash_to_scalar_psd2(&[tvk, index])?;
454 let record_nonce = N::g_scalar_multiply(&randomizer);
456 self.sample_record(burner_address, record_name, record_nonce, rng)
458 }
459
460 fn evaluate_view(
461 &self,
462 state: FinalizeGlobalState,
463 store: &dyn FinalizeStoreTrait<N>,
464 view_name: &Identifier<N>,
465 inputs: Vec<Value<N>>,
466 ) -> Result<Vec<Value<N>>> {
467 crate::view::evaluate_view_inner(state, store, self, view_name, inputs)
468 }
469}
470
471impl<N: Network> Stack<N> {
472 fn matches_record_internal(
474 &self,
475 record: &Record<N, Plaintext<N>>,
476 record_type: &RecordType<N>,
477 depth: usize,
478 ) -> Result<()> {
479 ensure!(depth <= N::MAX_DATA_DEPTH, "Plaintext exceeded maximum depth of {}", N::MAX_DATA_DEPTH);
481
482 let record_name = record_type.name();
484 ensure!(!Program::is_reserved_keyword(record_name), "Record name '{record_name}' is reserved");
486
487 ensure!(
489 record.owner().is_public() == record_type.owner().is_public(),
490 "Visibility of record entry 'owner' does not match"
491 );
492 ensure!(
493 record.owner().is_private() == record_type.owner().is_private(),
494 "Visibility of record entry 'owner' does not match"
495 );
496
497 let num_entries = record.data().len();
499 ensure!(num_entries <= N::MAX_DATA_ENTRIES, "'{record_name}' cannot exceed {} entries", N::MAX_DATA_ENTRIES);
500
501 let expected_num_entries = record_type.entries().len();
503 if expected_num_entries != num_entries {
504 bail!("'{record_name}' expected {expected_num_entries} entries, found {num_entries} entries")
505 }
506
507 for (i, ((expected_name, expected_type), (entry_name, entry))) in
509 record_type.entries().iter().zip_eq(record.data().iter()).enumerate()
510 {
511 if expected_name != entry_name {
513 bail!("Entry '{i}' in '{record_name}' is incorrect: expected '{expected_name}', found '{entry_name}'")
514 }
515 ensure!(!Program::is_reserved_keyword(entry_name), "Entry name '{entry_name}' is reserved");
517 self.matches_entry_internal(record_name, entry_name, entry, expected_type, depth + 1)?;
519 }
520
521 Ok(())
522 }
523
524 fn matches_entry_internal(
526 &self,
527 record_name: &Identifier<N>,
528 entry_name: &Identifier<N>,
529 entry: &Entry<N, Plaintext<N>>,
530 entry_type: &EntryType<N>,
531 depth: usize,
532 ) -> Result<()> {
533 match (entry, entry_type) {
534 (Entry::Constant(plaintext), EntryType::Constant(plaintext_type))
535 | (Entry::Public(plaintext), EntryType::Public(plaintext_type))
536 | (Entry::Private(plaintext), EntryType::Private(plaintext_type)) => {
537 match self.matches_plaintext_internal(plaintext, plaintext_type, depth) {
538 Ok(()) => Ok(()),
539 Err(error) => bail!("Invalid record entry '{record_name}.{entry_name}': {error}"),
540 }
541 }
542 _ => bail!(
543 "Type mismatch in record entry '{record_name}.{entry_name}':\n'{entry}'\n does not match\n'{entry_type}'"
544 ),
545 }
546 }
547
548 fn matches_plaintext_internal(
550 &self,
551 plaintext: &Plaintext<N>,
552 plaintext_type: &PlaintextType<N>,
553 depth: usize,
554 ) -> Result<()> {
555 ensure!(depth <= N::MAX_DATA_DEPTH, "Plaintext exceeded maximum depth of {}", N::MAX_DATA_DEPTH);
557
558 match plaintext_type {
560 PlaintextType::Literal(literal_type) => match plaintext {
561 Plaintext::Literal(literal, ..) => {
563 match literal.to_type() == *literal_type {
565 true => Ok(()),
566 false => bail!("'{literal}' is invalid: expected {literal_type}"),
567 }
568 }
569 Plaintext::Struct(..) => bail!("'{plaintext_type}' is invalid: expected literal, found struct"),
571 Plaintext::Array(..) => bail!("'{plaintext_type}' is invalid: expected literal, found array"),
573 },
574 PlaintextType::ExternalStruct(locator) => {
575 let external_stack = self.get_external_stack(locator.program_id())?;
576 let new_type = PlaintextType::Struct(*locator.resource());
577 external_stack.matches_plaintext_internal(plaintext, &new_type, depth)
578 }
579 PlaintextType::Struct(struct_name) => {
580 ensure!(!Program::is_reserved_keyword(struct_name), "Struct '{struct_name}' is reserved");
582
583 let Ok(struct_) = self.program().get_struct(struct_name) else {
585 bail!("Struct '{struct_name}' is not defined in the program")
586 };
587
588 if struct_.name() != struct_name {
590 bail!("Expected struct '{struct_name}', found struct '{}'", struct_.name())
591 }
592
593 let members = match plaintext {
595 Plaintext::Literal(..) => bail!("'{struct_name}' is invalid: expected struct, found literal"),
596 Plaintext::Struct(members, ..) => members,
597 Plaintext::Array(..) => bail!("'{struct_name}' is invalid: expected struct, found array"),
598 };
599
600 let num_members = members.len();
601 ensure!(
603 num_members >= N::MIN_STRUCT_ENTRIES,
604 "'{struct_name}' cannot be less than {} entries",
605 N::MIN_STRUCT_ENTRIES
606 );
607 ensure!(
609 num_members <= N::MAX_STRUCT_ENTRIES,
610 "'{struct_name}' cannot exceed {} entries",
611 N::MAX_STRUCT_ENTRIES
612 );
613
614 let expected_num_members = struct_.members().len();
616 if expected_num_members != num_members {
617 bail!("'{struct_name}' expected {expected_num_members} members, found {num_members} members")
618 }
619
620 for (i, ((expected_name, expected_type), (member_name, member))) in
622 struct_.members().iter().zip_eq(members.iter()).enumerate()
623 {
624 if expected_name != member_name {
626 bail!(
627 "Member '{i}' in '{struct_name}' is incorrect: expected '{expected_name}', found '{member_name}'"
628 )
629 }
630 ensure!(!Program::is_reserved_keyword(member_name), "Member name '{member_name}' is reserved");
632 self.matches_plaintext_internal(member, expected_type, depth + 1)?;
634 }
635
636 Ok(())
637 }
638 PlaintextType::Array(array_type) => match plaintext {
639 Plaintext::Literal(..) => bail!("'{plaintext_type}' is invalid: expected array, found literal"),
641 Plaintext::Struct(..) => bail!("'{plaintext_type}' is invalid: expected array, found struct"),
643 Plaintext::Array(array, ..) => {
645 let (actual_length, expected_length) = (array.len(), array_type.length());
647 if **expected_length as usize != actual_length {
648 bail!(
649 "'{plaintext_type}' is invalid: expected {expected_length} elements, found {actual_length} elements"
650 )
651 }
652 for element in array.iter() {
654 self.matches_plaintext_internal(element, array_type.next_element_type(), depth + 1)?;
655 }
656 Ok(())
657 }
658 },
659 }
660 }
661
662 fn matches_future_internal(&self, future: &Future<N>, locator: &Locator<N>, depth: usize) -> Result<()> {
664 ensure!(depth <= N::MAX_DATA_DEPTH, "Future exceeded maximum depth of {}", N::MAX_DATA_DEPTH);
666
667 ensure!(future.program_id() == locator.program_id(), "Future program ID does not match");
669
670 ensure!(future.function_name() == locator.resource(), "Future name does not match");
672
673 let external_stack = match locator.program_id() == self.program_id() {
675 true => None,
676 false => Some(self.get_external_stack(locator.program_id())?),
679 };
680 let function = match &external_stack {
682 Some(external_stack) => external_stack.get_function_ref(locator.resource())?,
683 None => self.get_function_ref(locator.resource())?,
684 };
685 let inputs = match function.finalize_logic() {
687 Some(finalize_logic) => finalize_logic.inputs(),
688 None => bail!("Function '{locator}' does not have a finalize block"),
689 };
690
691 ensure!(future.arguments().len() == inputs.len(), "Future arguments do not match");
693
694 for (argument, input) in future.arguments().iter().zip_eq(inputs.iter()) {
697 match (argument, input.finalize_type()) {
698 (Argument::Plaintext(plaintext), FinalizeType::Plaintext(plaintext_type)) => match &external_stack {
699 Some(external_stack) => {
700 external_stack.matches_plaintext_internal(plaintext, plaintext_type, depth + 1)?
701 }
702 None => self.matches_plaintext_internal(plaintext, plaintext_type, depth + 1)?,
703 },
704 (Argument::Future(future), FinalizeType::Future(locator)) => match &external_stack {
705 Some(external_stack) => external_stack.matches_future_internal(future, locator, depth + 1)?,
706 None => self.matches_future_internal(future, locator, depth + 1)?,
707 },
708 (Argument::DynamicFuture(_), FinalizeType::DynamicFuture) => {}
709 (_, input_type) => {
710 bail!("Argument type does not match input type: expected '{input_type}'")
711 }
712 }
713 }
714
715 Ok(())
716 }
717}
718
719#[cfg(test)]
720mod tests {
721 use super::*;
722 use console::network::MainnetV0;
723 use snarkvm_synthesizer_program::Program;
724
725 type CurrentNetwork = MainnetV0;
726
727 #[test]
730 fn test_contains_dynamic_call_with_static_calls() {
731 let process = Process::<CurrentNetwork>::load().unwrap();
733 let stack = process.get_stack("credits.aleo").unwrap();
734 let function_name = Identifier::from_str("transfer_public").unwrap();
736 assert!(!stack.contains_dynamic_call(&function_name).unwrap());
737 }
738
739 #[test]
742 fn test_contains_dynamic_call_with_dynamic_calls() {
743 let program = Program::<CurrentNetwork>::from_str(
745 r"
746program dynamic_test.aleo;
747
748function dynamic_func:
749 input r0 as field.public;
750 input r1 as field.public;
751 input r2 as field.public;
752 call.dynamic r0 r1 r2;",
753 )
754 .unwrap();
755 let process = Process::<CurrentNetwork>::load().unwrap();
757 process.lock().add_program(&program).unwrap();
758 let stack = process.get_stack("dynamic_test.aleo").unwrap();
759 let function_name = Identifier::from_str("dynamic_func").unwrap();
761 assert!(stack.contains_dynamic_call(&function_name).unwrap());
762 }
763
764 #[test]
767 fn test_contains_dynamic_call_transitive() {
768 let helper_program = Program::<CurrentNetwork>::from_str(
770 r"
771program helper.aleo;
772
773function dynamic_helper:
774 input r0 as field.public;
775 input r1 as field.public;
776 input r2 as field.public;
777 call.dynamic r0 r1 r2;",
778 )
779 .unwrap();
780 let caller_program = Program::<CurrentNetwork>::from_str(
782 r"
783import helper.aleo;
784
785program caller.aleo;
786
787function caller_func:
788 input r0 as field.public;
789 input r1 as field.public;
790 input r2 as field.public;
791 call helper.aleo/dynamic_helper r0 r1 r2;",
792 )
793 .unwrap();
794 let process = Process::<CurrentNetwork>::load().unwrap();
796 process.lock().add_program(&helper_program).unwrap();
797 process.lock().add_program(&caller_program).unwrap();
798 let stack = process.get_stack("caller.aleo").unwrap();
799 let function_name = Identifier::from_str("caller_func").unwrap();
801 assert!(stack.contains_dynamic_call(&function_name).unwrap());
802 }
803}