1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
// This file is part of Frontier.
//
// Copyright (c) 2019-2022 Moonsong Labs.
// Copyright (c) 2023 Parity Technologies (UK) Ltd.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.

//! Utilities to work with revert messages with support for backtraces and
//! consistent formatting.

use crate::solidity::{self, codec::bytes::UnboundedBytes};
use alloc::string::{String, ToString};
use fp_evm::{ExitRevert, PrecompileFailure};
use sp_std::vec::Vec;

/// Represent the result of a computation that can revert.
pub type MayRevert<T = ()> = Result<T, Revert>;

/// Generate an encoded revert from a simple String.
/// Returns a `PrecompileFailure` that fits in an `EvmResult::Err`.
pub fn revert(msg: impl Into<String>) -> PrecompileFailure {
	RevertReason::custom(msg).into()
}

/// Generate an encoded revert from a simple String.
/// Returns a `Vec<u8>` in case `PrecompileFailure` is too high level.
pub fn revert_as_bytes(msg: impl Into<String>) -> Vec<u8> {
	Revert::new(RevertReason::custom(msg)).to_encoded_bytes()
}

/// Generic error to build abi-encoded revert output.
/// See: https://docs.soliditylang.org/en/latest/control-structures.html?highlight=revert#revert
pub const ERROR_SELECTOR: u32 = 0x08c379a0;

#[derive(Clone, PartialEq, Eq)]
enum BacktracePart {
	Field(String),
	Tuple(usize),
	Array(usize),
}

/// Backtrace of an revert.
/// Built depth-first.
/// Implement `Display` to render the backtrace as a string.
#[derive(Default, PartialEq, Eq)]
pub struct Backtrace(Vec<BacktracePart>);

impl Backtrace {
	/// Create a new empty backtrace.
	pub fn new() -> Self {
		Self(Vec::new())
	}

	/// Check if the backtrace is empty.
	pub fn is_empty(&self) -> bool {
		self.0.is_empty()
	}
}

impl core::fmt::Display for Backtrace {
	fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> Result<(), core::fmt::Error> {
		for (i, part) in self.0.iter().rev().enumerate() {
			match (i, part) {
				(0, BacktracePart::Field(field)) => write!(f, "{field}")?,
				(_, BacktracePart::Field(field)) => write!(f, ".{field}")?,
				(_, BacktracePart::Tuple(index)) => write!(f, ".{index}")?,
				(_, BacktracePart::Array(index)) => write!(f, "[{index}]")?,
			}
		}
		Ok(())
	}
}

/// Possible revert reasons.
#[non_exhaustive]
#[derive(PartialEq, Eq)]
pub enum RevertReason {
	/// A custom revert reason if other variants are not appropriate.
	Custom(String),
	/// Tried to read data out of bounds.
	ReadOutOfBounds {
		/// What was being read?
		what: String,
	},
	/// An unknown selector has been provided.
	UnknownSelector,
	/// A value is too large to fit in the wanted type.
	/// For security reasons integers are always parsed as `uint256` then
	/// casted to the wanted type. If the value overflows this type then this
	/// revert is used.
	ValueIsTooLarge {
		/// What was being read?
		what: String,
	},
	/// A pointer (used for structs and arrays) points out of bounds.
	PointerToOutofBound,
	/// The reading cursor overflowed.
	/// This should realistically never happen as it would require an input
	/// of length larger than 2^64, which would cost too much to be included
	/// in a block.
	CursorOverflow,
	/// Used by a check that the input contains at least N static arguments.
	/// Often use to return early if the input is too short.
	ExpectedAtLeastNArguments(usize),
}

impl RevertReason {
	/// Create a `RevertReason::Custom` from anything that can be converted to a `String`.
	/// Argument is the custom revert message.
	pub fn custom(s: impl Into<String>) -> Self {
		RevertReason::Custom(s.into())
	}

	/// Create a `RevertReason::ReadOutOfBounds` from anything that can be converted to a `String`.
	/// Argument names what was expected to be read.
	pub fn read_out_of_bounds(what: impl Into<String>) -> Self {
		RevertReason::ReadOutOfBounds { what: what.into() }
	}

	/// Create a `RevertReason::ValueIsTooLarge` from anything that can be converted to a `String`.
	/// Argument names what was expected to be read.
	pub fn value_is_too_large(what: impl Into<String>) -> Self {
		RevertReason::ValueIsTooLarge { what: what.into() }
	}
}

impl core::fmt::Display for RevertReason {
	fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> Result<(), core::fmt::Error> {
		match self {
			RevertReason::Custom(s) => write!(f, "{s}"),
			RevertReason::ReadOutOfBounds { what } => {
				write!(f, "Tried to read {what} out of bounds")
			}
			RevertReason::UnknownSelector => write!(f, "Unknown selector"),
			RevertReason::ValueIsTooLarge { what } => write!(f, "Value is too large for {what}"),
			RevertReason::PointerToOutofBound => write!(f, "Pointer points to out of bound"),
			RevertReason::CursorOverflow => write!(f, "Reading cursor overflowed"),
			RevertReason::ExpectedAtLeastNArguments(n) => {
				write!(f, "Expected at least {n} arguments")
			}
		}
	}
}

/// An revert returned by various functions in precompile-utils.
/// Allows to dynamically construct the backtrace (backtrace) of the revert
/// and manage it in a typed way.
/// Can be transformed into a `PrecompileFailure::Revert` and `String`, and
/// implement `Display` and `Debug`.
#[derive(PartialEq, Eq)]
pub struct Revert {
	reason: RevertReason,
	backtrace: Backtrace,
}

impl Revert {
	/// Create a new `Revert` with a `RevertReason` and
	/// an empty backtrace.
	pub fn new(reason: RevertReason) -> Self {
		Self {
			reason,
			backtrace: Backtrace::new(),
		}
	}

	/// For all `RevertReason` variants that have a `what` field, change its value.
	/// Otherwise do nothing.
	/// It is useful when writing custom types `solidity::Codec` implementations using
	/// simpler types.
	pub fn change_what(mut self, what: impl Into<String>) -> Self {
		let what = what.into();

		self.reason = match self.reason {
			RevertReason::ReadOutOfBounds { .. } => RevertReason::ReadOutOfBounds { what },
			RevertReason::ValueIsTooLarge { .. } => RevertReason::ValueIsTooLarge { what },
			other => other,
		};

		self
	}

	/// Transforms the revert into its bytes representation (from a String).
	pub fn to_encoded_bytes(self) -> Vec<u8> {
		let bytes: Vec<u8> = self.into();
		solidity::encode_with_selector(ERROR_SELECTOR, UnboundedBytes::from(bytes))
	}
}

impl From<RevertReason> for Revert {
	fn from(a: RevertReason) -> Revert {
		Revert::new(a)
	}
}

impl core::fmt::Display for Revert {
	fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> Result<(), core::fmt::Error> {
		if !self.backtrace.is_empty() {
			write!(f, "{}: ", self.backtrace)?;
		}

		write!(f, "{}", self.reason)
	}
}

impl core::fmt::Debug for Revert {
	fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> Result<(), core::fmt::Error> {
		write!(f, "{}", self)
	}
}

impl From<Revert> for Vec<u8> {
	fn from(val: Revert) -> Self {
		val.to_string().into()
	}
}

/// Allows to inject backtrace data.
pub trait InjectBacktrace {
	/// Output type of the injection.
	/// Should be a type that can hold a backtrace.
	type Output;

	/// Occurs in a field.
	fn in_field(self, field: impl Into<String>) -> Self::Output;

	/// Occurs in a tuple.
	fn in_tuple(self, index: usize) -> Self::Output;

	/// Occurs in an array at provided index.
	fn in_array(self, index: usize) -> Self::Output;
}

/// Additional function for everything having a Backtrace.
pub trait BacktraceExt {
	/// Map last tuple entry into a field.
	/// Does nothing if last entry is not a tuple.
	/// As in Solidity structs are equivalent to tuples and are tricky to parse correctly,
	/// it allows to parse any struct as a tuple (with the correct implementation in this crate) and
	/// then map tuple indices to struct fields.
	fn map_in_tuple_to_field(self, fields: &[&'static str]) -> Self;
}

/// Additional functions for Revert and MayRevert.
pub trait RevertExt {
	/// Map the reason while keeping the same backtrace.
	fn map_reason(self, f: impl FnOnce(RevertReason) -> RevertReason) -> Self;
}

impl InjectBacktrace for RevertReason {
	// `RevertReason` cannot hold a backtrace, thus it wraps
	// it into a `Revert`.
	type Output = Revert;

	fn in_field(self, field: impl Into<String>) -> Revert {
		Revert::new(self).in_field(field)
	}

	fn in_array(self, index: usize) -> Revert {
		Revert::new(self).in_array(index)
	}

	fn in_tuple(self, index: usize) -> Revert {
		Revert::new(self).in_tuple(index)
	}
}

impl InjectBacktrace for Backtrace {
	type Output = Self;

	fn in_field(mut self, field: impl Into<String>) -> Self {
		self.0.push(BacktracePart::Field(field.into()));
		self
	}

	fn in_array(mut self, index: usize) -> Self {
		self.0.push(BacktracePart::Array(index));
		self
	}

	fn in_tuple(mut self, index: usize) -> Self {
		self.0.push(BacktracePart::Tuple(index));
		self
	}
}

impl BacktraceExt for Backtrace {
	fn map_in_tuple_to_field(mut self, fields: &[&'static str]) -> Self {
		if let Some(entry) = self.0.last_mut() {
			if let BacktracePart::Tuple(index) = *entry {
				if let Some(field) = fields.get(index) {
					*entry = BacktracePart::Field(field.to_string())
				}
			}
		}
		self
	}
}

impl InjectBacktrace for Revert {
	type Output = Self;

	fn in_field(mut self, field: impl Into<String>) -> Self {
		self.backtrace = self.backtrace.in_field(field);
		self
	}

	fn in_array(mut self, index: usize) -> Self {
		self.backtrace = self.backtrace.in_array(index);
		self
	}

	fn in_tuple(mut self, index: usize) -> Self {
		self.backtrace = self.backtrace.in_tuple(index);
		self
	}
}

impl RevertExt for Revert {
	fn map_reason(mut self, f: impl FnOnce(RevertReason) -> RevertReason) -> Self {
		self.reason = f(self.reason);
		self
	}
}

impl BacktraceExt for Revert {
	fn map_in_tuple_to_field(mut self, fields: &[&'static str]) -> Self {
		self.backtrace = self.backtrace.map_in_tuple_to_field(fields);
		self
	}
}

impl<T> InjectBacktrace for MayRevert<T> {
	type Output = Self;

	fn in_field(self, field: impl Into<String>) -> Self {
		self.map_err(|e| e.in_field(field))
	}

	fn in_array(self, index: usize) -> Self {
		self.map_err(|e| e.in_array(index))
	}

	fn in_tuple(self, index: usize) -> Self {
		self.map_err(|e| e.in_tuple(index))
	}
}

impl<T> RevertExt for MayRevert<T> {
	fn map_reason(self, f: impl FnOnce(RevertReason) -> RevertReason) -> Self {
		self.map_err(|e| e.map_reason(f))
	}
}

impl<T> BacktraceExt for MayRevert<T> {
	fn map_in_tuple_to_field(self, fields: &[&'static str]) -> Self {
		self.map_err(|e| e.map_in_tuple_to_field(fields))
	}
}

impl From<Revert> for PrecompileFailure {
	fn from(err: Revert) -> Self {
		PrecompileFailure::Revert {
			exit_status: ExitRevert::Reverted,
			output: err.to_encoded_bytes(),
		}
	}
}

impl From<RevertReason> for PrecompileFailure {
	fn from(err: RevertReason) -> Self {
		Revert::new(err).into()
	}
}