1use std::{
2 collections::VecDeque,
3 fmt::{self, Display},
4};
5
6use crate::{Cheatcode, Cheatcodes, CheatsCtxt, Error, Result, Vm::*};
7use alloy_dyn_abi::{DynSolValue, EventExt};
8use alloy_json_abi::Event;
9use alloy_primitives::{
10 Address, Bytes, LogData as RawLog, U256, hex, keccak256,
11 map::{AddressHashMap, HashMap, hash_map::Entry},
12};
13use alloy_sol_types::SolValue;
14use foundry_common::{abi::get_indexed_event, fmt::format_token};
15use foundry_evm_core::evm::FoundryEvmNetwork;
16use foundry_evm_traces::DecodedCallLog;
17use revm::{
18 context::{ContextTr, JournalTr},
19 interpreter::{
20 InstructionResult, Interpreter, InterpreterAction, interpreter_types::LoopControl,
21 },
22};
23
24use super::revert_handlers::RevertParameters;
25pub type ExpectedCallTracker = HashMap<Address, HashMap<Bytes, (ExpectedCallData, u64)>>;
35
36#[derive(Clone, Debug)]
37pub struct ExpectedCallData {
38 pub value: Option<U256>,
40 pub gas: Option<u64>,
42 pub min_gas: Option<u64>,
44 pub count: u64,
49 pub call_type: ExpectedCallType,
51}
52
53#[derive(Clone, Debug, PartialEq, Eq)]
55pub enum ExpectedCallType {
56 NonCount,
58 Count,
60}
61
62#[derive(Clone, Debug)]
64pub enum ExpectedRevertKind {
65 Default,
67 Cheatcode { pending_processing: bool },
73}
74
75#[derive(Clone, Debug)]
76pub struct ExpectedRevert {
77 pub reason: Option<Bytes>,
79 pub depth: usize,
81 pub kind: ExpectedRevertKind,
83 pub partial_match: bool,
85 pub reverter: Option<Address>,
87 pub reverted_by: Option<Address>,
89 pub max_depth: usize,
91 pub count: u64,
93 pub actual_count: u64,
95}
96
97#[derive(Clone, Debug)]
98pub struct ExpectedEmit {
99 pub depth: usize,
101 pub log: Option<RawLog>,
103 pub checks: [bool; 5],
110 pub address: Option<Address>,
112 pub anonymous: bool,
115 pub found: bool,
117 pub count: u64,
119 pub mismatch_error: Option<String>,
121}
122
123#[derive(Clone, Debug)]
124pub struct ExpectedCreate {
125 pub deployer: Address,
127 pub bytecode: Bytes,
129 pub create_scheme: CreateScheme,
131}
132
133#[derive(Clone, Debug)]
134pub enum CreateScheme {
135 Create,
136 Create2,
137}
138
139impl Display for CreateScheme {
140 fn fmt(&self, f: &mut fmt::Formatter) -> std::fmt::Result {
141 match self {
142 Self::Create => write!(f, "CREATE"),
143 Self::Create2 => write!(f, "CREATE2"),
144 }
145 }
146}
147
148impl From<revm::context_interface::CreateScheme> for CreateScheme {
149 fn from(scheme: revm::context_interface::CreateScheme) -> Self {
150 match scheme {
151 revm::context_interface::CreateScheme::Create => Self::Create,
152 revm::context_interface::CreateScheme::Create2 { .. } => Self::Create2,
153 _ => unimplemented!("Unsupported create scheme"),
154 }
155 }
156}
157
158impl CreateScheme {
159 pub const fn eq(&self, create_scheme: Self) -> bool {
160 matches!(
161 (self, create_scheme),
162 (Self::Create, Self::Create) | (Self::Create2, Self::Create2 { .. })
163 )
164 }
165}
166
167impl Cheatcode for expectCall_0Call {
168 fn apply<FEN: FoundryEvmNetwork>(&self, state: &mut Cheatcodes<FEN>) -> Result {
169 let Self { callee, data } = self;
170 expect_call(state, callee, data, None, None, None, 1, ExpectedCallType::NonCount)
171 }
172}
173
174impl Cheatcode for expectCall_1Call {
175 fn apply<FEN: FoundryEvmNetwork>(&self, state: &mut Cheatcodes<FEN>) -> Result {
176 let Self { callee, data, count } = self;
177 expect_call(state, callee, data, None, None, None, *count, ExpectedCallType::Count)
178 }
179}
180
181impl Cheatcode for expectCall_2Call {
182 fn apply<FEN: FoundryEvmNetwork>(&self, state: &mut Cheatcodes<FEN>) -> Result {
183 let Self { callee, msgValue, data } = self;
184 expect_call(state, callee, data, Some(msgValue), None, None, 1, ExpectedCallType::NonCount)
185 }
186}
187
188impl Cheatcode for expectCall_3Call {
189 fn apply<FEN: FoundryEvmNetwork>(&self, state: &mut Cheatcodes<FEN>) -> Result {
190 let Self { callee, msgValue, data, count } = self;
191 expect_call(
192 state,
193 callee,
194 data,
195 Some(msgValue),
196 None,
197 None,
198 *count,
199 ExpectedCallType::Count,
200 )
201 }
202}
203
204impl Cheatcode for expectCall_4Call {
205 fn apply<FEN: FoundryEvmNetwork>(&self, state: &mut Cheatcodes<FEN>) -> Result {
206 let Self { callee, msgValue, gas, data } = self;
207 expect_call(
208 state,
209 callee,
210 data,
211 Some(msgValue),
212 Some(*gas),
213 None,
214 1,
215 ExpectedCallType::NonCount,
216 )
217 }
218}
219
220impl Cheatcode for expectCall_5Call {
221 fn apply<FEN: FoundryEvmNetwork>(&self, state: &mut Cheatcodes<FEN>) -> Result {
222 let Self { callee, msgValue, gas, data, count } = self;
223 expect_call(
224 state,
225 callee,
226 data,
227 Some(msgValue),
228 Some(*gas),
229 None,
230 *count,
231 ExpectedCallType::Count,
232 )
233 }
234}
235
236impl Cheatcode for expectCallMinGas_0Call {
237 fn apply<FEN: FoundryEvmNetwork>(&self, state: &mut Cheatcodes<FEN>) -> Result {
238 let Self { callee, msgValue, minGas, data } = self;
239 expect_call(
240 state,
241 callee,
242 data,
243 Some(msgValue),
244 None,
245 Some(*minGas),
246 1,
247 ExpectedCallType::NonCount,
248 )
249 }
250}
251
252impl Cheatcode for expectCallMinGas_1Call {
253 fn apply<FEN: FoundryEvmNetwork>(&self, state: &mut Cheatcodes<FEN>) -> Result {
254 let Self { callee, msgValue, minGas, data, count } = self;
255 expect_call(
256 state,
257 callee,
258 data,
259 Some(msgValue),
260 None,
261 Some(*minGas),
262 *count,
263 ExpectedCallType::Count,
264 )
265 }
266}
267
268impl Cheatcode for expectEmit_0Call {
269 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
270 let Self { checkTopic1, checkTopic2, checkTopic3, checkData } = *self;
271 expect_emit(
272 ccx.state,
273 ccx.ecx.journal().depth(),
274 [true, checkTopic1, checkTopic2, checkTopic3, checkData],
275 None,
276 false,
277 1,
278 )
279 }
280}
281
282impl Cheatcode for expectEmit_1Call {
283 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
284 let Self { checkTopic1, checkTopic2, checkTopic3, checkData, emitter } = *self;
285 expect_emit(
286 ccx.state,
287 ccx.ecx.journal().depth(),
288 [true, checkTopic1, checkTopic2, checkTopic3, checkData],
289 Some(emitter),
290 false,
291 1,
292 )
293 }
294}
295
296impl Cheatcode for expectEmit_2Call {
297 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
298 let Self {} = self;
299 expect_emit(ccx.state, ccx.ecx.journal().depth(), [true; 5], None, false, 1)
300 }
301}
302
303impl Cheatcode for expectEmit_3Call {
304 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
305 let Self { emitter } = *self;
306 expect_emit(ccx.state, ccx.ecx.journal().depth(), [true; 5], Some(emitter), false, 1)
307 }
308}
309
310impl Cheatcode for expectEmit_4Call {
311 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
312 let Self { checkTopic1, checkTopic2, checkTopic3, checkData, count } = *self;
313 expect_emit(
314 ccx.state,
315 ccx.ecx.journal().depth(),
316 [true, checkTopic1, checkTopic2, checkTopic3, checkData],
317 None,
318 false,
319 count,
320 )
321 }
322}
323
324impl Cheatcode for expectEmit_5Call {
325 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
326 let Self { checkTopic1, checkTopic2, checkTopic3, checkData, emitter, count } = *self;
327 expect_emit(
328 ccx.state,
329 ccx.ecx.journal().depth(),
330 [true, checkTopic1, checkTopic2, checkTopic3, checkData],
331 Some(emitter),
332 false,
333 count,
334 )
335 }
336}
337
338impl Cheatcode for expectEmit_6Call {
339 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
340 let Self { count } = *self;
341 expect_emit(ccx.state, ccx.ecx.journal().depth(), [true; 5], None, false, count)
342 }
343}
344
345impl Cheatcode for expectEmit_7Call {
346 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
347 let Self { emitter, count } = *self;
348 expect_emit(ccx.state, ccx.ecx.journal().depth(), [true; 5], Some(emitter), false, count)
349 }
350}
351
352impl Cheatcode for expectEmitAnonymous_0Call {
353 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
354 let Self { checkTopic0, checkTopic1, checkTopic2, checkTopic3, checkData } = *self;
355 expect_emit(
356 ccx.state,
357 ccx.ecx.journal().depth(),
358 [checkTopic0, checkTopic1, checkTopic2, checkTopic3, checkData],
359 None,
360 true,
361 1,
362 )
363 }
364}
365
366impl Cheatcode for expectEmitAnonymous_1Call {
367 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
368 let Self { checkTopic0, checkTopic1, checkTopic2, checkTopic3, checkData, emitter } = *self;
369 expect_emit(
370 ccx.state,
371 ccx.ecx.journal().depth(),
372 [checkTopic0, checkTopic1, checkTopic2, checkTopic3, checkData],
373 Some(emitter),
374 true,
375 1,
376 )
377 }
378}
379
380impl Cheatcode for expectEmitAnonymous_2Call {
381 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
382 let Self {} = self;
383 expect_emit(ccx.state, ccx.ecx.journal().depth(), [true; 5], None, true, 1)
384 }
385}
386
387impl Cheatcode for expectEmitAnonymous_3Call {
388 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
389 let Self { emitter } = *self;
390 expect_emit(ccx.state, ccx.ecx.journal().depth(), [true; 5], Some(emitter), true, 1)
391 }
392}
393
394impl Cheatcode for expectCreateCall {
395 fn apply<FEN: FoundryEvmNetwork>(&self, state: &mut Cheatcodes<FEN>) -> Result {
396 let Self { bytecode, deployer } = self;
397 expect_create(state, bytecode.clone(), *deployer, CreateScheme::Create)
398 }
399}
400
401impl Cheatcode for expectCreate2Call {
402 fn apply<FEN: FoundryEvmNetwork>(&self, state: &mut Cheatcodes<FEN>) -> Result {
403 let Self { bytecode, deployer } = self;
404 expect_create(state, bytecode.clone(), *deployer, CreateScheme::Create2)
405 }
406}
407
408impl Cheatcode for expectTip20LogoURIUpdatedCall {
409 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
410 let Self { token, updater, newLogoURI } = self;
411 expect_logo_uri_updated(ccx, token, updater, newLogoURI)
412 }
413}
414
415impl Cheatcode for expectLogoURIUpdatedCall {
416 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
417 let Self { token, updater, newLogoURI } = self;
418 expect_logo_uri_updated(ccx, token, updater, newLogoURI)
419 }
420}
421
422fn expect_logo_uri_updated<FEN: FoundryEvmNetwork>(
423 ccx: &mut CheatsCtxt<'_, '_, FEN>,
424 token: &Address,
425 updater: &Address,
426 new_logo_uri: &str,
427) -> Result {
428 let expected_emit = ExpectedEmit {
429 depth: ccx.ecx.journal().depth(),
430 log: Some(RawLog::new_unchecked(
431 vec![keccak256("LogoURIUpdated(address,string)"), updater.into_word()],
432 new_logo_uri.abi_encode().into(),
433 )),
434 checks: [true, true, false, false, true],
435 address: Some(*token),
436 anonymous: false,
437 found: false,
438 count: 1,
439 mismatch_error: None,
440 };
441 ccx.state.expected_emits.push_back((expected_emit, Default::default()));
442 Ok(Default::default())
443}
444
445impl Cheatcode for expectRevert_0Call {
446 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
447 let Self {} = self;
448 expect_revert(ccx.state, None, ccx.ecx.journal().depth(), false, false, None, 1)
449 }
450}
451
452impl Cheatcode for expectRevert_1Call {
453 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
454 let Self { revertData } = self;
455 expect_revert(
456 ccx.state,
457 Some(revertData.as_ref()),
458 ccx.ecx.journal().depth(),
459 false,
460 false,
461 None,
462 1,
463 )
464 }
465}
466
467impl Cheatcode for expectRevert_2Call {
468 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
469 let Self { revertData } = self;
470 expect_revert(ccx.state, Some(revertData), ccx.ecx.journal().depth(), false, false, None, 1)
471 }
472}
473
474impl Cheatcode for expectRevert_3Call {
475 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
476 let Self { reverter } = self;
477 expect_revert(ccx.state, None, ccx.ecx.journal().depth(), false, false, Some(*reverter), 1)
478 }
479}
480
481impl Cheatcode for expectRevert_4Call {
482 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
483 let Self { revertData, reverter } = self;
484 expect_revert(
485 ccx.state,
486 Some(revertData.as_ref()),
487 ccx.ecx.journal().depth(),
488 false,
489 false,
490 Some(*reverter),
491 1,
492 )
493 }
494}
495
496impl Cheatcode for expectRevert_5Call {
497 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
498 let Self { revertData, reverter } = self;
499 expect_revert(
500 ccx.state,
501 Some(revertData),
502 ccx.ecx.journal().depth(),
503 false,
504 false,
505 Some(*reverter),
506 1,
507 )
508 }
509}
510
511impl Cheatcode for expectRevert_6Call {
512 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
513 let Self { count } = self;
514 expect_revert(ccx.state, None, ccx.ecx.journal().depth(), false, false, None, *count)
515 }
516}
517
518impl Cheatcode for expectRevert_7Call {
519 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
520 let Self { revertData, count } = self;
521 expect_revert(
522 ccx.state,
523 Some(revertData.as_ref()),
524 ccx.ecx.journal().depth(),
525 false,
526 false,
527 None,
528 *count,
529 )
530 }
531}
532
533impl Cheatcode for expectRevert_8Call {
534 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
535 let Self { revertData, count } = self;
536 expect_revert(
537 ccx.state,
538 Some(revertData),
539 ccx.ecx.journal().depth(),
540 false,
541 false,
542 None,
543 *count,
544 )
545 }
546}
547
548impl Cheatcode for expectRevert_9Call {
549 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
550 let Self { reverter, count } = self;
551 expect_revert(
552 ccx.state,
553 None,
554 ccx.ecx.journal().depth(),
555 false,
556 false,
557 Some(*reverter),
558 *count,
559 )
560 }
561}
562
563impl Cheatcode for expectRevert_10Call {
564 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
565 let Self { revertData, reverter, count } = self;
566 expect_revert(
567 ccx.state,
568 Some(revertData.as_ref()),
569 ccx.ecx.journal().depth(),
570 false,
571 false,
572 Some(*reverter),
573 *count,
574 )
575 }
576}
577
578impl Cheatcode for expectRevert_11Call {
579 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
580 let Self { revertData, reverter, count } = self;
581 expect_revert(
582 ccx.state,
583 Some(revertData),
584 ccx.ecx.journal().depth(),
585 false,
586 false,
587 Some(*reverter),
588 *count,
589 )
590 }
591}
592
593impl Cheatcode for expectPartialRevert_0Call {
594 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
595 let Self { revertData } = self;
596 expect_revert(
597 ccx.state,
598 Some(revertData.as_ref()),
599 ccx.ecx.journal().depth(),
600 false,
601 true,
602 None,
603 1,
604 )
605 }
606}
607
608impl Cheatcode for expectPartialRevert_1Call {
609 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
610 let Self { revertData, reverter } = self;
611 expect_revert(
612 ccx.state,
613 Some(revertData.as_ref()),
614 ccx.ecx.journal().depth(),
615 false,
616 true,
617 Some(*reverter),
618 1,
619 )
620 }
621}
622
623impl Cheatcode for _expectCheatcodeRevert_0Call {
624 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
625 expect_revert(ccx.state, None, ccx.ecx.journal().depth(), true, false, None, 1)
626 }
627}
628
629impl Cheatcode for _expectCheatcodeRevert_1Call {
630 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
631 let Self { revertData } = self;
632 expect_revert(
633 ccx.state,
634 Some(revertData.as_ref()),
635 ccx.ecx.journal().depth(),
636 true,
637 false,
638 None,
639 1,
640 )
641 }
642}
643
644impl Cheatcode for _expectCheatcodeRevert_2Call {
645 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
646 let Self { revertData } = self;
647 expect_revert(ccx.state, Some(revertData), ccx.ecx.journal().depth(), true, false, None, 1)
648 }
649}
650
651impl Cheatcode for expectSafeMemoryCall {
652 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
653 let Self { min, max } = *self;
654 expect_safe_memory(ccx.state, min, max, ccx.ecx.journal().depth().try_into()?)
655 }
656}
657
658impl Cheatcode for stopExpectSafeMemoryCall {
659 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
660 let Self {} = self;
661 ccx.state.allowed_mem_writes.remove(&ccx.ecx.journal().depth().try_into()?);
662 Ok(Default::default())
663 }
664}
665
666impl Cheatcode for expectSafeMemoryCallCall {
667 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
668 let Self { min, max } = *self;
669 expect_safe_memory(ccx.state, min, max, (ccx.ecx.journal().depth() + 1).try_into()?)
670 }
671}
672
673impl RevertParameters for ExpectedRevert {
674 fn reverter(&self) -> Option<Address> {
675 self.reverter
676 }
677
678 fn reason(&self) -> Option<&[u8]> {
679 self.reason.as_ref().map(|b| &***b)
680 }
681
682 fn partial_match(&self) -> bool {
683 self.partial_match
684 }
685}
686
687#[expect(clippy::too_many_arguments)] fn expect_call<FEN: FoundryEvmNetwork>(
705 state: &mut Cheatcodes<FEN>,
706 target: &Address,
707 calldata: &Bytes,
708 value: Option<&U256>,
709 mut gas: Option<u64>,
710 mut min_gas: Option<u64>,
711 count: u64,
712 call_type: ExpectedCallType,
713) -> Result {
714 let expecteds = state.expected_calls.entry(*target).or_default();
715
716 if let Some(val) = value
717 && *val > U256::ZERO
718 {
719 let positive_value_cost_stipend = 2300;
722 if let Some(gas) = &mut gas {
723 *gas += positive_value_cost_stipend;
724 }
725 if let Some(min_gas) = &mut min_gas {
726 *min_gas += positive_value_cost_stipend;
727 }
728 }
729
730 match call_type {
731 ExpectedCallType::Count => {
732 ensure!(
736 !expecteds.contains_key(calldata),
737 "counted expected calls can only bet set once"
738 );
739 expecteds.insert(
740 calldata.clone(),
741 (ExpectedCallData { value: value.copied(), gas, min_gas, count, call_type }, 0),
742 );
743 }
744 ExpectedCallType::NonCount => {
745 match expecteds.entry(calldata.clone()) {
748 Entry::Occupied(mut entry) => {
749 let (expected, _) = entry.get_mut();
750 ensure!(
752 expected.call_type == ExpectedCallType::NonCount,
753 "cannot overwrite a counted expectCall with a non-counted expectCall"
754 );
755 expected.count += 1;
756 }
757 Entry::Vacant(entry) => {
759 entry.insert((
760 ExpectedCallData { value: value.copied(), gas, min_gas, count, call_type },
761 0,
762 ));
763 }
764 }
765 }
766 }
767
768 Ok(Default::default())
769}
770
771fn expect_emit<FEN: FoundryEvmNetwork>(
772 state: &mut Cheatcodes<FEN>,
773 depth: usize,
774 checks: [bool; 5],
775 address: Option<Address>,
776 anonymous: bool,
777 count: u64,
778) -> Result {
779 let expected_emit = ExpectedEmit {
780 depth,
781 checks,
782 address,
783 found: false,
784 log: None,
785 anonymous,
786 count,
787 mismatch_error: None,
788 };
789 if let Some(found_emit_pos) = state.expected_emits.iter().position(|(emit, _)| emit.found) {
790 state.expected_emits.insert(found_emit_pos, (expected_emit, Default::default()));
793 } else {
794 state.expected_emits.push_back((expected_emit, Default::default()));
796 }
797
798 Ok(Default::default())
799}
800
801pub(crate) fn handle_expect_emit<FEN: FoundryEvmNetwork>(
802 state: &mut Cheatcodes<FEN>,
803 log: &alloy_primitives::Log,
804 mut interpreter: Option<&mut Interpreter>,
805) -> Option<&'static str> {
806 let mut failure_reason = None;
809
810 if state.expected_emits.iter().all(|(expected, _)| expected.found) {
821 return failure_reason;
822 }
823
824 for (expected_emit, _) in &state.expected_emits {
826 if expected_emit.count == 0
827 && !expected_emit.found
828 && let Some(expected_log) = &expected_emit.log
829 && checks_topics_and_data(expected_emit.checks, expected_log, log)
830 && (expected_emit.address.is_none_or(|address| address == log.address))
832 {
833 if let Some(interpreter) = &mut interpreter {
834 interpreter.bytecode.set_action(InterpreterAction::new_return(
837 InstructionResult::Revert,
838 Error::encode("log emitted but expected 0 times"),
839 interpreter.gas,
840 ));
841 } else {
842 failure_reason = Some("log emitted but expected 0 times");
843 }
844
845 return failure_reason;
846 }
847 }
848
849 let should_fill_logs = state.expected_emits.iter().any(|(expected, _)| expected.log.is_none());
850 let index_to_fill_or_check = if should_fill_logs {
851 state
854 .expected_emits
855 .iter()
856 .position(|(emit, _)| emit.found)
857 .unwrap_or(state.expected_emits.len())
858 .saturating_sub(1)
859 } else {
860 state.expected_emits.iter().position(|(emit, _)| !emit.found && emit.count > 0).unwrap_or(0)
864 };
865
866 if !should_fill_logs
868 && state.expected_emits.iter().all(|(emit, _)| emit.found || emit.count == 0)
869 {
870 return failure_reason;
871 }
872
873 let (mut event_to_fill_or_check, mut count_map) = state
874 .expected_emits
875 .remove(index_to_fill_or_check)
876 .expect("we should have an emit to fill or check");
877
878 let Some(expected) = &event_to_fill_or_check.log else {
879 if event_to_fill_or_check.anonymous || !log.topics().is_empty() {
882 event_to_fill_or_check.log = Some(log.data.clone());
883 state
885 .expected_emits
886 .insert(index_to_fill_or_check, (event_to_fill_or_check, count_map));
887 } else if let Some(interpreter) = &mut interpreter {
888 interpreter.bytecode.set_action(InterpreterAction::new_return(
889 InstructionResult::Revert,
890 Error::encode("use vm.expectEmitAnonymous to match anonymous events"),
891 interpreter.gas,
892 ));
893 } else {
894 failure_reason = Some("use vm.expectEmitAnonymous to match anonymous events");
895 }
896
897 return failure_reason;
898 };
899
900 match count_map.entry(log.address) {
902 Entry::Occupied(mut entry) => {
903 let log_count_map = entry.get_mut();
904 log_count_map.insert(&log.data);
905 }
906 Entry::Vacant(entry) => {
907 let mut log_count_map = LogCountMap::new(&event_to_fill_or_check);
908 if log_count_map.satisfies_checks(&log.data) {
909 log_count_map.insert(&log.data);
910 entry.insert(log_count_map);
911 }
912 }
913 }
914
915 event_to_fill_or_check.found = || -> bool {
916 if !checks_topics_and_data(event_to_fill_or_check.checks, expected, log) {
917 let (expected_decoded, actual_decoded) = if let Some(signatures_identifier) =
921 state.signatures_identifier()
922 && !event_to_fill_or_check.anonymous
923 {
924 (
925 decode_event(signatures_identifier, expected),
926 decode_event(signatures_identifier, log),
927 )
928 } else {
929 (None, None)
930 };
931 event_to_fill_or_check.mismatch_error = Some(get_emit_mismatch_message(
932 event_to_fill_or_check.checks,
933 expected,
934 log,
935 event_to_fill_or_check.anonymous,
936 expected_decoded.as_ref(),
937 actual_decoded.as_ref(),
938 ));
939 return false;
940 }
941
942 if event_to_fill_or_check.address.is_some_and(|addr| addr != log.address) {
944 event_to_fill_or_check.mismatch_error = Some(format!(
945 "log emitter mismatch: expected={:#x}, got={:#x}",
946 event_to_fill_or_check.address.unwrap(),
947 log.address
948 ));
949 return false;
950 }
951
952 let expected_count = event_to_fill_or_check.count;
953 match event_to_fill_or_check.address {
954 Some(emitter) => count_map
955 .get(&emitter)
956 .is_some_and(|log_map| log_map.count(&log.data) >= expected_count),
957 None => count_map
958 .values()
959 .find(|log_map| log_map.satisfies_checks(&log.data))
960 .is_some_and(|map| map.count(&log.data) >= expected_count),
961 }
962 }();
963
964 if event_to_fill_or_check.found {
967 state.expected_emits.push_back((event_to_fill_or_check, count_map));
968 } else {
969 state.expected_emits.push_front((event_to_fill_or_check, count_map));
972 }
973
974 failure_reason
975}
976
977pub type ExpectedEmitTracker = VecDeque<(ExpectedEmit, AddressHashMap<LogCountMap>)>;
982
983#[derive(Clone, Debug, Default)]
984pub struct LogCountMap {
985 checks: [bool; 5],
986 expected_log: RawLog,
987 map: HashMap<RawLog, u64>,
988}
989
990impl LogCountMap {
991 fn new(expected_emit: &ExpectedEmit) -> Self {
993 Self {
994 checks: expected_emit.checks,
995 expected_log: expected_emit.log.clone().expect("log should be filled here"),
996 map: Default::default(),
997 }
998 }
999
1000 fn insert(&mut self, log: &RawLog) -> bool {
1006 if self.map.contains_key(log) {
1008 self.map.entry(log.clone()).and_modify(|c| *c += 1);
1009
1010 return true;
1011 }
1012
1013 if !self.satisfies_checks(log) {
1014 return false;
1015 }
1016
1017 self.map.entry(log.clone()).and_modify(|c| *c += 1).or_insert(1);
1018
1019 true
1020 }
1021
1022 fn satisfies_checks(&self, log: &RawLog) -> bool {
1024 checks_topics_and_data(self.checks, &self.expected_log, log)
1025 }
1026
1027 pub fn count(&self, log: &RawLog) -> u64 {
1028 if !self.satisfies_checks(log) {
1029 return 0;
1030 }
1031
1032 self.count_unchecked()
1033 }
1034
1035 pub fn count_unchecked(&self) -> u64 {
1036 self.map.values().sum()
1037 }
1038}
1039
1040fn expect_create<FEN: FoundryEvmNetwork>(
1041 state: &mut Cheatcodes<FEN>,
1042 bytecode: Bytes,
1043 deployer: Address,
1044 create_scheme: CreateScheme,
1045) -> Result {
1046 let expected_create = ExpectedCreate { bytecode, deployer, create_scheme };
1047 state.expected_creates.push(expected_create);
1048
1049 Ok(Default::default())
1050}
1051
1052fn expect_revert<FEN: FoundryEvmNetwork>(
1053 state: &mut Cheatcodes<FEN>,
1054 reason: Option<&[u8]>,
1055 depth: usize,
1056 cheatcode: bool,
1057 partial_match: bool,
1058 reverter: Option<Address>,
1059 count: u64,
1060) -> Result {
1061 ensure!(
1062 state.expected_revert.is_none(),
1063 "you must call another function prior to expecting a second revert"
1064 );
1065 state.expected_revert = Some(ExpectedRevert {
1066 reason: reason.map(Bytes::copy_from_slice),
1067 depth,
1068 kind: if cheatcode {
1069 ExpectedRevertKind::Cheatcode { pending_processing: true }
1070 } else {
1071 ExpectedRevertKind::Default
1072 },
1073 partial_match,
1074 reverter,
1075 reverted_by: None,
1076 max_depth: depth,
1077 count,
1078 actual_count: 0,
1079 });
1080 Ok(Default::default())
1081}
1082
1083fn checks_topics_and_data(checks: [bool; 5], expected: &RawLog, log: &RawLog) -> bool {
1084 if log.topics().len() != expected.topics().len() {
1085 return false;
1086 }
1087
1088 if !log
1090 .topics()
1091 .iter()
1092 .enumerate()
1093 .filter(|(i, _)| checks[*i])
1094 .all(|(i, topic)| topic == &expected.topics()[i])
1095 {
1096 return false;
1097 }
1098
1099 if checks[4] && expected.data.as_ref() != log.data.as_ref() {
1101 return false;
1102 }
1103
1104 true
1105}
1106
1107fn decode_event(
1108 identifier: &foundry_evm_traces::identifier::SignaturesIdentifier,
1109 log: &RawLog,
1110) -> Option<DecodedCallLog> {
1111 let topics = log.topics();
1112 if topics.is_empty() {
1113 return None;
1114 }
1115 let t0 = topics[0]; let event = foundry_common::block_on(
1118 identifier.identify_event_with_indexed_count(t0, topics.len().saturating_sub(1)),
1119 )?;
1120
1121 let has_indexed_info = event.inputs.iter().any(|p| p.indexed);
1123 let indexed_event = if has_indexed_info { event } else { get_indexed_event(event, log) };
1125
1126 if let Ok(decoded) = indexed_event.decode_log(log) {
1128 let params = reconstruct_params(&indexed_event, &decoded);
1129
1130 let decoded_params = params
1131 .into_iter()
1132 .zip(indexed_event.inputs.iter())
1133 .map(|(param, input)| (input.name.clone(), format_token(¶m)))
1134 .collect();
1135
1136 return Some(DecodedCallLog {
1137 name: Some(indexed_event.name),
1138 params: Some(decoded_params),
1139 });
1140 }
1141
1142 None
1143}
1144
1145fn reconstruct_params(event: &Event, decoded: &alloy_dyn_abi::DecodedEvent) -> Vec<DynSolValue> {
1147 let mut indexed = 0;
1148 let mut unindexed = 0;
1149 let mut inputs = vec![];
1150 for input in &event.inputs {
1151 if input.indexed && indexed < decoded.indexed.len() {
1152 inputs.push(decoded.indexed[indexed].clone());
1153 indexed += 1;
1154 } else if unindexed < decoded.body.len() {
1155 inputs.push(decoded.body[unindexed].clone());
1156 unindexed += 1;
1157 }
1158 }
1159 inputs
1160}
1161
1162pub(crate) fn get_emit_mismatch_message(
1164 checks: [bool; 5],
1165 expected: &RawLog,
1166 actual: &RawLog,
1167 is_anonymous: bool,
1168 expected_decoded: Option<&DecodedCallLog>,
1169 actual_decoded: Option<&DecodedCallLog>,
1170) -> String {
1171 if actual.topics().len() != expected.topics().len() {
1175 let expected_name = expected_decoded.and_then(|d| d.name.as_deref()).unwrap_or("log");
1176 let actual_name = actual_decoded.and_then(|d| d.name.as_deref()).unwrap_or("log");
1177 let expected_topics = checked_topic_count(expected, is_anonymous);
1178 let actual_topics = checked_topic_count(actual, is_anonymous);
1179
1180 if expected_name == actual_name {
1181 return format!(
1182 "{actual_name} indexed topic count mismatch: expected {expected_topics}, got {actual_topics}"
1183 );
1184 }
1185
1186 return name_mismatched_logs(expected_decoded, actual_decoded);
1187 }
1188
1189 if !is_anonymous
1191 && checks[0]
1192 && (!expected.topics().is_empty() && !actual.topics().is_empty())
1193 && expected.topics()[0] != actual.topics()[0]
1194 {
1195 return name_mismatched_logs(expected_decoded, actual_decoded);
1196 }
1197
1198 let expected_data = expected.data.as_ref();
1199 let actual_data = actual.data.as_ref();
1200
1201 if checks[4] && expected_data != actual_data {
1203 if expected_data.len() != actual_data.len()
1205 || !expected_data.len().is_multiple_of(32)
1206 || expected_data.is_empty()
1207 {
1208 return name_mismatched_logs(expected_decoded, actual_decoded);
1209 }
1210 }
1211
1212 let mut mismatches = Vec::new();
1214
1215 for (i, (expected_topic, actual_topic)) in
1217 expected.topics().iter().zip(actual.topics().iter()).enumerate()
1218 {
1219 if i == 0 && !is_anonymous {
1221 continue;
1222 }
1223
1224 if i < checks.len() && checks[i] && expected_topic != actual_topic {
1226 let param_idx = if is_anonymous {
1227 i } else {
1229 i - 1 };
1231 mismatches
1232 .push(format!("param {param_idx}: expected={expected_topic}, got={actual_topic}"));
1233 }
1234 }
1235
1236 if checks[4] && expected_data != actual_data {
1238 let num_indexed_params = if is_anonymous {
1239 expected.topics().len()
1240 } else {
1241 expected.topics().len().saturating_sub(1)
1242 };
1243
1244 for (i, (expected_chunk, actual_chunk)) in
1245 expected_data.chunks(32).zip(actual_data.chunks(32)).enumerate()
1246 {
1247 if expected_chunk != actual_chunk {
1248 let param_idx = num_indexed_params + i;
1249 mismatches.push(format!(
1250 "param {}: expected={}, got={}",
1251 param_idx,
1252 hex::encode_prefixed(expected_chunk),
1253 hex::encode_prefixed(actual_chunk)
1254 ));
1255 }
1256 }
1257 }
1258
1259 if mismatches.is_empty() {
1260 name_mismatched_logs(expected_decoded, actual_decoded)
1261 } else {
1262 let event_prefix = match (expected_decoded, actual_decoded) {
1264 (Some(expected_dec), Some(actual_dec)) if expected_dec.name == actual_dec.name => {
1265 format!(
1266 "{} param mismatch",
1267 expected_dec.name.as_ref().unwrap_or(&"log".to_string())
1268 )
1269 }
1270 _ => {
1271 if is_anonymous {
1272 "anonymous log mismatch".to_string()
1273 } else {
1274 "log mismatch".to_string()
1275 }
1276 }
1277 };
1278
1279 let detailed_mismatches = if let (Some(expected_dec), Some(actual_dec)) =
1281 (expected_decoded, actual_decoded)
1282 && let (Some(expected_params), Some(actual_params)) =
1283 (&expected_dec.params, &actual_dec.params)
1284 {
1285 mismatches
1286 .into_iter()
1287 .map(|basic_mismatch| {
1288 if let Some(param_idx) = basic_mismatch
1290 .split(' ')
1291 .nth(1)
1292 .and_then(|s| s.trim_end_matches(':').parse::<usize>().ok())
1293 && param_idx < expected_params.len()
1294 && param_idx < actual_params.len()
1295 {
1296 let (expected_name, expected_value) = &expected_params[param_idx];
1297 let (_actual_name, actual_value) = &actual_params[param_idx];
1298 let param_name = if expected_name.is_empty() {
1299 &format!("param{param_idx}")
1300 } else {
1301 expected_name
1302 };
1303 return format!(
1304 "{param_name}: expected={expected_value}, got={actual_value}",
1305 );
1306 }
1307 basic_mismatch
1308 })
1309 .collect::<Vec<_>>()
1310 } else {
1311 mismatches
1312 };
1313
1314 format!("{} at {}", event_prefix, detailed_mismatches.join(", "))
1315 }
1316}
1317
1318fn name_mismatched_logs(
1320 expected_decoded: Option<&DecodedCallLog>,
1321 actual_decoded: Option<&DecodedCallLog>,
1322) -> String {
1323 let expected_name = expected_decoded.and_then(|d| d.name.as_deref()).unwrap_or("log");
1324 let actual_name = actual_decoded.and_then(|d| d.name.as_deref()).unwrap_or("log");
1325 format!("{actual_name} != expected {expected_name}")
1326}
1327
1328fn checked_topic_count(log: &RawLog, is_anonymous: bool) -> usize {
1329 if is_anonymous { log.topics().len() } else { log.topics().len().saturating_sub(1) }
1330}
1331
1332fn expect_safe_memory<FEN: FoundryEvmNetwork>(
1333 state: &mut Cheatcodes<FEN>,
1334 start: u64,
1335 end: u64,
1336 depth: u64,
1337) -> Result {
1338 ensure!(start < end, "memory range start ({start}) is greater than end ({end})");
1339 #[expect(clippy::single_range_in_vec_init)] let offsets = state.allowed_mem_writes.entry(depth).or_insert_with(|| vec![0..0x60]);
1341 offsets.push(start..end);
1342 Ok(Default::default())
1343}