use crate::solidity::{self, codec::bytes::UnboundedBytes};
use alloc::string::{String, ToString};
use fp_evm::{ExitRevert, PrecompileFailure};
use sp_std::vec::Vec;
pub type MayRevert<T = ()> = Result<T, Revert>;
pub fn revert(msg: impl Into<String>) -> PrecompileFailure {
RevertReason::custom(msg).into()
}
pub fn revert_as_bytes(msg: impl Into<String>) -> Vec<u8> {
Revert::new(RevertReason::custom(msg)).to_encoded_bytes()
}
pub const ERROR_SELECTOR: u32 = 0x08c379a0;
#[derive(Clone, PartialEq, Eq)]
enum BacktracePart {
Field(String),
Tuple(usize),
Array(usize),
}
#[derive(Default, PartialEq, Eq)]
pub struct Backtrace(Vec<BacktracePart>);
impl Backtrace {
pub fn new() -> Self {
Self(Vec::new())
}
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(())
}
}
#[non_exhaustive]
#[derive(PartialEq, Eq)]
pub enum RevertReason {
Custom(String),
ReadOutOfBounds {
what: String,
},
UnknownSelector,
ValueIsTooLarge {
what: String,
},
PointerToOutofBound,
CursorOverflow,
ExpectedAtLeastNArguments(usize),
}
impl RevertReason {
pub fn custom(s: impl Into<String>) -> Self {
RevertReason::Custom(s.into())
}
pub fn read_out_of_bounds(what: impl Into<String>) -> Self {
RevertReason::ReadOutOfBounds { what: what.into() }
}
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")
}
}
}
}
#[derive(PartialEq, Eq)]
pub struct Revert {
reason: RevertReason,
backtrace: Backtrace,
}
impl Revert {
pub fn new(reason: RevertReason) -> Self {
Self {
reason,
backtrace: Backtrace::new(),
}
}
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
}
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()
}
}
pub trait InjectBacktrace {
type Output;
fn in_field(self, field: impl Into<String>) -> Self::Output;
fn in_tuple(self, index: usize) -> Self::Output;
fn in_array(self, index: usize) -> Self::Output;
}
pub trait BacktraceExt {
fn map_in_tuple_to_field(self, fields: &[&'static str]) -> Self;
}
pub trait RevertExt {
fn map_reason(self, f: impl FnOnce(RevertReason) -> RevertReason) -> Self;
}
impl InjectBacktrace for RevertReason {
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()
}
}