1use crate::{Cheatcode, CheatcodesCore, CheatsCtxt, Result, Vm::*};
2use alloy_primitives::{Address, Bytes, U256, map::HashMap};
3use foundry_evm_core::evm::FoundryEvmNetwork;
4use revm::{
5 bytecode::Bytecode,
6 context::{ContextTr, JournalTr},
7};
8use std::{
9 cmp::Ordering,
10 collections::{BTreeMap, VecDeque},
11};
12
13#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)]
15pub struct MockCallDataContext {
16 pub calldata: Bytes,
18 pub value: Option<U256>,
20}
21
22#[derive(Clone, Debug)]
24pub struct MockCallReturnData {
25 pub reverts: bool,
27 pub data: Bytes,
29}
30
31impl PartialOrd for MockCallDataContext {
32 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
33 Some(self.cmp(other))
34 }
35}
36
37impl Ord for MockCallDataContext {
38 fn cmp(&self, other: &Self) -> Ordering {
39 self.calldata.cmp(&other.calldata).reverse().then(self.value.cmp(&other.value).reverse())
45 }
46}
47
48impl Cheatcode for clearMockedCallsCall {
49 fn apply_context(&self, state: &mut CheatcodesCore) -> Result {
50 let Self {} = self;
51 state.mocked_calls = Default::default();
52 Ok(Default::default())
53 }
54}
55
56impl Cheatcode for mockCall_0Call {
57 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
58 let Self { callee, data, returnData } = self;
59 let _ = make_acc_non_empty(callee, ccx)?;
60
61 mock_call(&mut ccx.state.core.mocked_calls, callee, data, None, returnData, false);
62 Ok(Default::default())
63 }
64}
65
66impl Cheatcode for mockCall_1Call {
67 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
68 let Self { callee, msgValue, data, returnData } = self;
69 let _ = make_acc_non_empty(callee, ccx)?;
70
71 mock_call(
72 &mut ccx.state.core.mocked_calls,
73 callee,
74 data,
75 Some(msgValue),
76 returnData,
77 false,
78 );
79 Ok(Default::default())
80 }
81}
82
83impl Cheatcode for mockCall_2Call {
84 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
85 let Self { callee, data, returnData } = self;
86 let _ = make_acc_non_empty(callee, ccx)?;
87
88 mock_call(
89 &mut ccx.state.core.mocked_calls,
90 callee,
91 &Bytes::from(*data),
92 None,
93 returnData,
94 false,
95 );
96 Ok(Default::default())
97 }
98}
99
100impl Cheatcode for mockCall_3Call {
101 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
102 let Self { callee, msgValue, data, returnData } = self;
103 let _ = make_acc_non_empty(callee, ccx)?;
104
105 mock_call(
106 &mut ccx.state.core.mocked_calls,
107 callee,
108 &Bytes::from(*data),
109 Some(msgValue),
110 returnData,
111 false,
112 );
113 Ok(Default::default())
114 }
115}
116
117impl Cheatcode for mockCall_4Call {
118 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
119 let Self { callee, data, returnData, injectCode } = self;
120 if *injectCode {
121 let _ = make_acc_non_empty(callee, ccx)?;
122 }
123
124 mock_call(&mut ccx.state.core.mocked_calls, callee, data, None, returnData, false);
125 Ok(Default::default())
126 }
127}
128
129impl Cheatcode for mockCalls_0Call {
130 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
131 let Self { callee, data, returnData } = self;
132 let _ = make_acc_non_empty(callee, ccx)?;
133
134 mock_calls(&mut ccx.state.core.mocked_calls, callee, data, None, returnData, false);
135 Ok(Default::default())
136 }
137}
138
139impl Cheatcode for mockCalls_1Call {
140 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
141 let Self { callee, msgValue, data, returnData } = self;
142 let _ = make_acc_non_empty(callee, ccx)?;
143
144 mock_calls(
145 &mut ccx.state.core.mocked_calls,
146 callee,
147 data,
148 Some(msgValue),
149 returnData,
150 false,
151 );
152 Ok(Default::default())
153 }
154}
155
156impl Cheatcode for mockCallRevert_0Call {
157 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
158 let Self { callee, data, revertData } = self;
159 let _ = make_acc_non_empty(callee, ccx)?;
160
161 mock_call(&mut ccx.state.core.mocked_calls, callee, data, None, revertData, true);
162 Ok(Default::default())
163 }
164}
165
166impl Cheatcode for mockCallRevert_1Call {
167 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
168 let Self { callee, msgValue, data, revertData } = self;
169 let _ = make_acc_non_empty(callee, ccx)?;
170
171 mock_call(&mut ccx.state.core.mocked_calls, callee, data, Some(msgValue), revertData, true);
172 Ok(Default::default())
173 }
174}
175
176impl Cheatcode for mockCallRevert_2Call {
177 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
178 let Self { callee, data, revertData } = self;
179 let _ = make_acc_non_empty(callee, ccx)?;
180
181 mock_call(
182 &mut ccx.state.core.mocked_calls,
183 callee,
184 &Bytes::from(*data),
185 None,
186 revertData,
187 true,
188 );
189 Ok(Default::default())
190 }
191}
192
193impl Cheatcode for mockCallRevert_3Call {
194 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
195 let Self { callee, msgValue, data, revertData } = self;
196 let _ = make_acc_non_empty(callee, ccx)?;
197
198 mock_call(
199 &mut ccx.state.core.mocked_calls,
200 callee,
201 &Bytes::from(*data),
202 Some(msgValue),
203 revertData,
204 true,
205 );
206 Ok(Default::default())
207 }
208}
209
210impl Cheatcode for mockFunctionCall {
211 fn apply_context(&self, state: &mut CheatcodesCore) -> Result {
212 let Self { callee, target, data } = self;
213 state.mocked_functions.entry(*callee).or_default().insert(data.clone(), *target);
214
215 Ok(Default::default())
216 }
217}
218
219fn mock_call(
220 mocks: &mut HashMap<Address, BTreeMap<MockCallDataContext, VecDeque<MockCallReturnData>>>,
221 callee: &Address,
222 cdata: &Bytes,
223 value: Option<&U256>,
224 rdata: &Bytes,
225 reverts: bool,
226) {
227 mock_calls(mocks, callee, cdata, value, std::slice::from_ref(rdata), reverts)
228}
229
230fn mock_calls(
231 mocks: &mut HashMap<Address, BTreeMap<MockCallDataContext, VecDeque<MockCallReturnData>>>,
232 callee: &Address,
233 cdata: &Bytes,
234 value: Option<&U256>,
235 rdata_vec: &[Bytes],
236 reverts: bool,
237) {
238 mocks.entry(*callee).or_default().insert(
239 MockCallDataContext { calldata: cdata.clone(), value: value.copied() },
240 rdata_vec.iter().map(|rdata| MockCallReturnData { reverts, data: rdata.clone() }).collect(),
241 );
242}
243
244fn make_acc_non_empty<FEN: FoundryEvmNetwork>(
247 callee: &Address,
248 ccx: &mut CheatsCtxt<'_, '_, FEN>,
249) -> Result {
250 let empty_bytecode = {
251 let acc = ccx.ecx.journal_mut().load_account(*callee)?;
252 acc.info.code.as_ref().is_none_or(Bytecode::is_empty)
253 };
254 if empty_bytecode {
255 let code = Bytecode::new_raw(Bytes::from_static(&[0u8]));
256 ccx.ecx.journal_mut().set_code(*callee, code);
257 }
258
259 Ok(Default::default())
260}
261
262pub(crate) fn find_mock_returns<'a, T>(
267 mocks: &'a mut BTreeMap<MockCallDataContext, VecDeque<T>>,
268 input: &Bytes,
269 value: Option<U256>,
270) -> Option<&'a mut VecDeque<T>> {
271 let ctx = MockCallDataContext { calldata: input.clone(), value };
272 mocks
274 .range_mut(ctx..)
275 .find(|(mock, _)| {
276 input.get(..mock.calldata.len()) == Some(&mock.calldata[..])
277 && mock.value.is_none_or(|mock_value| Some(mock_value) == value)
278 })
279 .map(|(_, v)| v)
280}
281
282pub(crate) fn advance_mock_returns<T>(queue: &mut VecDeque<T>) {
284 if queue.len() > 1 {
285 queue.pop_front();
286 }
287}
288
289#[cfg(test)]
290mod tests {
291 use super::*;
292
293 fn mock(
294 calldata: &'static [u8],
295 value: Option<u64>,
296 result: u8,
297 ) -> (MockCallDataContext, VecDeque<u8>) {
298 (
299 MockCallDataContext {
300 calldata: Bytes::from_static(calldata),
301 value: value.map(U256::from),
302 },
303 VecDeque::from([result]),
304 )
305 }
306
307 #[test]
308 fn mock_matching_precedence() {
309 let mut mocks = BTreeMap::from([
310 mock(b"call", Some(1), 1),
311 mock(b"call", Some(4), 9),
312 mock(b"call", None, 2),
313 mock(b"cal", Some(1), 3),
314 mock(b"cal", Some(0), 4),
315 mock(b"cal", Some(2), 5),
316 mock(b"cal", None, 6),
317 mock(b"ca", Some(3), 7),
318 mock(b"call-longer", Some(1), 8),
319 ]);
320
321 for (input, value, expected) in [
322 (b"call", Some(1), 1),
323 (b"call", Some(3), 2),
324 (b"call", None, 2),
325 (b"calx", Some(1), 3),
326 (b"calx", Some(3), 6),
327 ] {
328 let queue =
329 find_mock_returns(&mut mocks, &Bytes::from_static(input), value.map(U256::from))
330 .unwrap();
331 assert_eq!(queue.front(), Some(&expected), "input: {input:?}, value: {value:?}");
332 }
333 }
334
335 #[test]
336 fn absent_transfer_value_does_not_match_zero() {
337 let mut mocks = BTreeMap::from([mock(b"call", Some(0), 1)]);
338 let input = Bytes::from_static(b"call");
339 assert!(find_mock_returns(&mut mocks, &input, None).is_none());
340 assert_eq!(
341 find_mock_returns(&mut mocks, &input, Some(U256::ZERO)).unwrap().front(),
342 Some(&1)
343 );
344
345 mocks.extend([mock(b"cal", None, 2)]);
346 assert_eq!(find_mock_returns(&mut mocks, &input, None).unwrap().front(), Some(&2));
347 }
348
349 #[test]
350 fn empty_matched_queue_is_distinct_from_no_match() {
351 let mut mocks = BTreeMap::from([mock(b"cal", None, 1)]);
352 mocks.insert(
353 MockCallDataContext { calldata: Bytes::from_static(b"call"), value: None },
354 VecDeque::new(),
355 );
356
357 let queue = find_mock_returns(&mut mocks, &Bytes::from_static(b"call"), None).unwrap();
358 assert!(queue.is_empty());
359 assert!(find_mock_returns(&mut mocks, &Bytes::from_static(b"other"), None).is_none());
360 }
361
362 #[test]
363 fn advancing_returns_keeps_last_result() {
364 let mut queue = VecDeque::from([1, 2, 3]);
365 advance_mock_returns(&mut queue);
366 assert_eq!(queue, VecDeque::from([2, 3]));
367 advance_mock_returns(&mut queue);
368 assert_eq!(queue, VecDeque::from([3]));
369 advance_mock_returns(&mut queue);
370 assert_eq!(queue, VecDeque::from([3]));
371
372 queue.clear();
373 advance_mock_returns(&mut queue);
374 assert!(queue.is_empty());
375 }
376}