precompile_utils/solidity/
revert.rs1use crate::solidity::{self, codec::bytes::UnboundedBytes};
23use alloc::{
24 string::{String, ToString},
25 vec::Vec,
26};
27use fp_evm::{ExitRevert, PrecompileFailure};
28
29pub type MayRevert<T = ()> = Result<T, Revert>;
31
32pub fn revert(msg: impl Into<String>) -> PrecompileFailure {
35 RevertReason::custom(msg).into()
36}
37
38pub fn revert_as_bytes(msg: impl Into<String>) -> Vec<u8> {
41 Revert::new(RevertReason::custom(msg)).to_encoded_bytes()
42}
43
44pub const ERROR_SELECTOR: u32 = 0x08c379a0;
47
48#[derive(Clone, PartialEq, Eq)]
49enum BacktracePart {
50 Field(String),
51 Tuple(usize),
52 Array(usize),
53}
54
55#[derive(Default, PartialEq, Eq)]
59pub struct Backtrace(Vec<BacktracePart>);
60
61impl Backtrace {
62 pub fn new() -> Self {
64 Self(Vec::new())
65 }
66
67 pub fn is_empty(&self) -> bool {
69 self.0.is_empty()
70 }
71}
72
73impl core::fmt::Display for Backtrace {
74 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> Result<(), core::fmt::Error> {
75 for (i, part) in self.0.iter().rev().enumerate() {
76 match (i, part) {
77 (0, BacktracePart::Field(field)) => write!(f, "{field}")?,
78 (_, BacktracePart::Field(field)) => write!(f, ".{field}")?,
79 (_, BacktracePart::Tuple(index)) => write!(f, ".{index}")?,
80 (_, BacktracePart::Array(index)) => write!(f, "[{index}]")?,
81 }
82 }
83 Ok(())
84 }
85}
86
87#[non_exhaustive]
89#[derive(PartialEq, Eq)]
90pub enum RevertReason {
91 Custom(String),
93 ReadOutOfBounds {
95 what: String,
97 },
98 UnknownSelector,
100 ValueIsTooLarge {
105 what: String,
107 },
108 PointerToOutofBound,
110 CursorOverflow,
115 ExpectedAtLeastNArguments(usize),
118}
119
120impl RevertReason {
121 pub fn custom(s: impl Into<String>) -> Self {
124 RevertReason::Custom(s.into())
125 }
126
127 pub fn read_out_of_bounds(what: impl Into<String>) -> Self {
130 RevertReason::ReadOutOfBounds { what: what.into() }
131 }
132
133 pub fn value_is_too_large(what: impl Into<String>) -> Self {
136 RevertReason::ValueIsTooLarge { what: what.into() }
137 }
138}
139
140impl core::fmt::Display for RevertReason {
141 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> Result<(), core::fmt::Error> {
142 match self {
143 RevertReason::Custom(s) => write!(f, "{s}"),
144 RevertReason::ReadOutOfBounds { what } => {
145 write!(f, "Tried to read {what} out of bounds")
146 }
147 RevertReason::UnknownSelector => write!(f, "Unknown selector"),
148 RevertReason::ValueIsTooLarge { what } => write!(f, "Value is too large for {what}"),
149 RevertReason::PointerToOutofBound => write!(f, "Pointer points to out of bound"),
150 RevertReason::CursorOverflow => write!(f, "Reading cursor overflowed"),
151 RevertReason::ExpectedAtLeastNArguments(n) => {
152 write!(f, "Expected at least {n} arguments")
153 }
154 }
155 }
156}
157
158#[derive(PartialEq, Eq)]
164pub struct Revert {
165 reason: RevertReason,
166 backtrace: Backtrace,
167}
168
169impl Revert {
170 pub fn new(reason: RevertReason) -> Self {
173 Self {
174 reason,
175 backtrace: Backtrace::new(),
176 }
177 }
178
179 pub fn change_what(mut self, what: impl Into<String>) -> Self {
184 let what = what.into();
185
186 self.reason = match self.reason {
187 RevertReason::ReadOutOfBounds { .. } => RevertReason::ReadOutOfBounds { what },
188 RevertReason::ValueIsTooLarge { .. } => RevertReason::ValueIsTooLarge { what },
189 other => other,
190 };
191
192 self
193 }
194
195 pub fn to_encoded_bytes(self) -> Vec<u8> {
197 let bytes: Vec<u8> = self.into();
198 solidity::encode_with_selector(ERROR_SELECTOR, UnboundedBytes::from(bytes))
199 }
200}
201
202impl From<RevertReason> for Revert {
203 fn from(a: RevertReason) -> Revert {
204 Revert::new(a)
205 }
206}
207
208impl core::fmt::Display for Revert {
209 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> Result<(), core::fmt::Error> {
210 if !self.backtrace.is_empty() {
211 write!(f, "{}: ", self.backtrace)?;
212 }
213
214 write!(f, "{}", self.reason)
215 }
216}
217
218impl core::fmt::Debug for Revert {
219 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> Result<(), core::fmt::Error> {
220 write!(f, "{self}")
221 }
222}
223
224impl From<Revert> for Vec<u8> {
225 fn from(val: Revert) -> Self {
226 val.to_string().into()
227 }
228}
229
230pub trait InjectBacktrace {
232 type Output;
235
236 fn in_field(self, field: impl Into<String>) -> Self::Output;
238
239 fn in_tuple(self, index: usize) -> Self::Output;
241
242 fn in_array(self, index: usize) -> Self::Output;
244}
245
246pub trait BacktraceExt {
248 fn map_in_tuple_to_field(self, fields: &[&'static str]) -> Self;
254}
255
256pub trait RevertExt {
258 fn map_reason(self, f: impl FnOnce(RevertReason) -> RevertReason) -> Self;
260}
261
262impl InjectBacktrace for RevertReason {
263 type Output = Revert;
266
267 fn in_field(self, field: impl Into<String>) -> Revert {
268 Revert::new(self).in_field(field)
269 }
270
271 fn in_array(self, index: usize) -> Revert {
272 Revert::new(self).in_array(index)
273 }
274
275 fn in_tuple(self, index: usize) -> Revert {
276 Revert::new(self).in_tuple(index)
277 }
278}
279
280impl InjectBacktrace for Backtrace {
281 type Output = Self;
282
283 fn in_field(mut self, field: impl Into<String>) -> Self {
284 self.0.push(BacktracePart::Field(field.into()));
285 self
286 }
287
288 fn in_array(mut self, index: usize) -> Self {
289 self.0.push(BacktracePart::Array(index));
290 self
291 }
292
293 fn in_tuple(mut self, index: usize) -> Self {
294 self.0.push(BacktracePart::Tuple(index));
295 self
296 }
297}
298
299impl BacktraceExt for Backtrace {
300 fn map_in_tuple_to_field(mut self, fields: &[&'static str]) -> Self {
301 if let Some(entry) = self.0.last_mut() {
302 if let BacktracePart::Tuple(index) = *entry {
303 if let Some(field) = fields.get(index) {
304 *entry = BacktracePart::Field(field.to_string())
305 }
306 }
307 }
308 self
309 }
310}
311
312impl InjectBacktrace for Revert {
313 type Output = Self;
314
315 fn in_field(mut self, field: impl Into<String>) -> Self {
316 self.backtrace = self.backtrace.in_field(field);
317 self
318 }
319
320 fn in_array(mut self, index: usize) -> Self {
321 self.backtrace = self.backtrace.in_array(index);
322 self
323 }
324
325 fn in_tuple(mut self, index: usize) -> Self {
326 self.backtrace = self.backtrace.in_tuple(index);
327 self
328 }
329}
330
331impl RevertExt for Revert {
332 fn map_reason(mut self, f: impl FnOnce(RevertReason) -> RevertReason) -> Self {
333 self.reason = f(self.reason);
334 self
335 }
336}
337
338impl BacktraceExt for Revert {
339 fn map_in_tuple_to_field(mut self, fields: &[&'static str]) -> Self {
340 self.backtrace = self.backtrace.map_in_tuple_to_field(fields);
341 self
342 }
343}
344
345impl<T> InjectBacktrace for MayRevert<T> {
346 type Output = Self;
347
348 fn in_field(self, field: impl Into<String>) -> Self {
349 self.map_err(|e| e.in_field(field))
350 }
351
352 fn in_array(self, index: usize) -> Self {
353 self.map_err(|e| e.in_array(index))
354 }
355
356 fn in_tuple(self, index: usize) -> Self {
357 self.map_err(|e| e.in_tuple(index))
358 }
359}
360
361impl<T> RevertExt for MayRevert<T> {
362 fn map_reason(self, f: impl FnOnce(RevertReason) -> RevertReason) -> Self {
363 self.map_err(|e| e.map_reason(f))
364 }
365}
366
367impl<T> BacktraceExt for MayRevert<T> {
368 fn map_in_tuple_to_field(self, fields: &[&'static str]) -> Self {
369 self.map_err(|e| e.map_in_tuple_to_field(fields))
370 }
371}
372
373impl From<Revert> for PrecompileFailure {
374 fn from(err: Revert) -> Self {
375 PrecompileFailure::Revert {
376 exit_status: ExitRevert::Reverted,
377 output: err.to_encoded_bytes(),
378 }
379 }
380}
381
382impl From<RevertReason> for PrecompileFailure {
383 fn from(err: RevertReason) -> Self {
384 Revert::new(err).into()
385 }
386}