1use crate::{Error, Result};
2use alloy_primitives::{Address, Bytes, address, hex};
3use alloy_sol_types::{SolError, SolValue};
4use foundry_common::ContractsByArtifact;
5use foundry_evm_core::decode::RevertDecoder;
6use revm::interpreter::{InstructionResult, return_ok};
7use spec::Vm;
8
9use super::{
10 assume::{AcceptableRevertParameters, AssumeNoRevert},
11 expect::ExpectedRevert,
12};
13
14static DUMMY_CALL_OUTPUT: Bytes = Bytes::from_static(&[0u8; 8192]);
21
22const DUMMY_CREATE_ADDRESS: Address = address!("0x0000000000000000000000000000000000000001");
24
25pub(crate) trait RevertParameters {
27 fn reverter(&self) -> Option<Address>;
28 fn reason(&self) -> Option<&[u8]>;
29 fn partial_match(&self) -> bool;
30}
31
32impl RevertParameters for AcceptableRevertParameters {
33 fn reverter(&self) -> Option<Address> {
34 self.reverter
35 }
36
37 fn reason(&self) -> Option<&[u8]> {
38 Some(&self.reason)
39 }
40
41 fn partial_match(&self) -> bool {
42 self.partial_match
43 }
44}
45
46fn handle_revert(
48 is_cheatcode: bool,
49 revert_params: &impl RevertParameters,
50 status: InstructionResult,
51 retdata: &Bytes,
52 known_contracts: &Option<ContractsByArtifact>,
53 reverter: Option<&Address>,
54) -> Result<(), Error> {
55 if let (Some(expected_reverter), Some(&actual_reverter)) = (revert_params.reverter(), reverter)
57 && expected_reverter != actual_reverter
58 {
59 return Err(fmt_err!(
60 "Reverter != expected reverter: {} != {}",
61 actual_reverter,
62 expected_reverter
63 ));
64 }
65
66 let expected_reason = revert_params.reason();
67 let Some(expected_reason) = expected_reason else {
69 return Ok(());
70 };
71
72 let actual_revert = if retdata.is_empty() && !expected_reason.is_empty() {
73 if status == InstructionResult::Revert {
74 bail!("call reverted as expected, but without data");
75 }
76 RevertDecoder::new().decode(retdata, Some(status)).into_bytes()
77 } else {
78 retdata.to_vec()
79 };
80
81 if revert_params.partial_match()
83 && let (Some(actual_prefix), Some(expected_prefix)) =
84 (actual_revert.get(..4), expected_reason.get(..4))
85 && actual_prefix == expected_prefix
86 {
87 return Ok(());
88 }
89
90 if actual_revert == expected_reason {
92 return Ok(());
93 }
94
95 let actual_reason = decode_revert(actual_revert);
97
98 if actual_reason == expected_reason
99 || (is_cheatcode && memchr::memmem::find(&actual_reason, expected_reason).is_some())
100 {
101 return Ok(());
102 }
103
104 let (actual, expected) = if let Some(contracts) = known_contracts {
105 let decoder = RevertDecoder::new().with_abis(contracts.values().map(|c| &c.abi));
106 (
107 &decoder.decode(actual_reason.as_slice(), Some(status)),
108 &decoder.decode(expected_reason, Some(status)),
109 )
110 } else {
111 (&stringify(&actual_reason), &stringify(expected_reason))
112 };
113
114 if expected == actual {
118 return Err(fmt_err!(
119 "Error != expected error: {actual} (raw {}) != {expected} (raw {})",
120 hex::encode_prefixed(retdata),
121 hex::encode_prefixed(expected_reason)
122 ));
123 }
124
125 Err(fmt_err!("Error != expected error: {} != {}", actual, expected))
126}
127
128pub(crate) fn handle_assume_no_revert(
129 assume_no_revert: &AssumeNoRevert,
130 status: InstructionResult,
131 retdata: &Bytes,
132 known_contracts: &Option<ContractsByArtifact>,
133) -> Result<()> {
134 if assume_no_revert.reasons.is_empty() {
137 Ok(())
138 } else {
139 assume_no_revert
140 .reasons
141 .iter()
142 .find_map(|reason| {
143 handle_revert(
144 false,
145 reason,
146 status,
147 retdata,
148 known_contracts,
149 assume_no_revert.reverted_by.as_ref(),
150 )
151 .ok()
152 })
153 .ok_or_else(|| retdata.clone().into())
154 }
155}
156
157pub(crate) fn handle_expect_revert(
158 is_cheatcode: bool,
159 is_create: bool,
160 internal_expect_revert: bool,
161 expected_revert: &ExpectedRevert,
162 status: InstructionResult,
163 retdata: Bytes,
164 known_contracts: &Option<ContractsByArtifact>,
165) -> Result<(Option<Address>, Bytes)> {
166 let success_return = || {
167 if is_create {
168 (Some(DUMMY_CREATE_ADDRESS), Bytes::new())
169 } else {
170 (None, DUMMY_CALL_OUTPUT.clone())
171 }
172 };
173
174 if !is_cheatcode && !internal_expect_revert {
176 ensure!(
177 expected_revert.max_depth > expected_revert.depth,
178 "call didn't revert at a lower depth than cheatcode call depth"
179 );
180 }
181
182 if expected_revert.count == 0 {
183 if expected_revert.reverter.is_none() && expected_revert.reason.is_none() {
185 ensure!(
186 matches!(status, return_ok!()),
187 "call reverted when it was expected not to revert"
188 );
189 return Ok(success_return());
190 }
191
192 let mut reason_match = expected_revert.reason.as_ref().map(|_| false);
194 let mut reverter_match = expected_revert.reverter.as_ref().map(|_| false);
195
196 if matches!(status, return_ok!()) {
199 Ok(success_return())
201 } else {
202 if let (Some(expected_reverter), Some(actual_reverter)) =
207 (expected_revert.reverter, expected_revert.reverted_by)
208 && expected_reverter == actual_reverter
209 {
210 reverter_match = Some(true);
211 }
212
213 let expected_reason = expected_revert.reason();
215 if let Some(expected_reason) = expected_reason {
216 let mut actual_revert: Vec<u8> = retdata.to_vec();
217 actual_revert = decode_revert(actual_revert);
218
219 if actual_revert == expected_reason {
220 reason_match = Some(true);
221 }
222 }
223
224 match (reason_match, reverter_match) {
225 (Some(true), Some(true)) => Err(fmt_err!(
226 "expected 0 reverts with reason: {}, from address: {}, but got one",
227 stringify(expected_reason.unwrap_or_default()),
228 expected_revert.reverter.unwrap()
229 )),
230 (Some(true), None) => Err(fmt_err!(
231 "expected 0 reverts with reason: {}, but got one",
232 stringify(expected_reason.unwrap_or_default())
233 )),
234 (None, Some(true)) => Err(fmt_err!(
235 "expected 0 reverts from address: {}, but got one",
236 expected_revert.reverter.unwrap()
237 )),
238 _ => {
239 let decoded_revert = decode_revert(retdata.to_vec());
242
243 if let Some(reverter) = expected_revert.reverter {
245 if expected_revert.reason.is_some() {
246 Err(fmt_err!(
247 "call reverted with '{}' from {}, but expected 0 reverts with reason '{}' from {}",
248 stringify(&decoded_revert),
249 expected_revert.reverted_by.unwrap_or_default(),
250 stringify(expected_reason.unwrap_or_default()),
251 reverter
252 ))
253 } else {
254 Err(fmt_err!(
255 "call reverted with '{}' from {}, but expected 0 reverts from {}",
256 stringify(&decoded_revert),
257 expected_revert.reverted_by.unwrap_or_default(),
258 reverter
259 ))
260 }
261 } else {
262 Err(fmt_err!(
263 "call reverted with '{}' when it was expected not to revert",
264 stringify(&decoded_revert)
265 ))
266 }
267 }
268 }
269 }
270 } else {
271 ensure!(!matches!(status, return_ok!()), "next call did not revert as expected");
272
273 handle_revert(
274 is_cheatcode,
275 expected_revert,
276 status,
277 &retdata,
278 known_contracts,
279 expected_revert.reverted_by.as_ref(),
280 )?;
281 Ok(success_return())
282 }
283}
284
285fn stringify(data: &[u8]) -> String {
286 if let Ok(s) = String::abi_decode(data) {
287 return s;
288 }
289 if data.is_ascii() {
290 return std::str::from_utf8(data).unwrap().to_owned();
291 }
292 hex::encode_prefixed(data)
293}
294
295fn decode_revert(revert: Vec<u8>) -> Vec<u8> {
296 if matches!(
297 revert.get(..4).map(|s| s.try_into().unwrap()),
298 Some(Vm::CheatcodeError::SELECTOR | alloy_sol_types::Revert::SELECTOR)
299 ) && let Ok(decoded) = Vec::<u8>::abi_decode(&revert[4..])
300 {
301 return decoded;
302 }
303 revert
304}