Skip to content

Commit

Permalink
feat: add PanicKind enum
Browse files Browse the repository at this point in the history
  • Loading branch information
DaniPopes committed May 22, 2023
1 parent 33d3040 commit 101fa06
Show file tree
Hide file tree
Showing 3 changed files with 191 additions and 31 deletions.
4 changes: 3 additions & 1 deletion crates/sol-types/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,9 @@ mod errors;
pub use errors::{Error, Result};

mod types;
pub use types::{data_type as sol_data, Panic, Revert, SolCall, SolError, SolStruct, SolType};
pub use types::{
data_type as sol_data, Panic, PanicKind, Revert, SolCall, SolError, SolStruct, SolType,
};

mod util;

Expand Down
216 changes: 187 additions & 29 deletions crates/sol-types/src/types/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use crate::{
token::{PackedSeqToken, TokenSeq, WordToken},
Result, SolType,
};
use core::fmt::Display;
use core::fmt;
use ethers_primitives::U256;

/// Solidity Error (a tuple with a selector)
Expand Down Expand Up @@ -73,7 +73,7 @@ pub trait SolError: Sized {

/// Represents a standard Solidity revert. These are thrown by
/// `require(condition, reason)` statements in Solidity.
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
#[derive(Clone, PartialEq, Eq, Hash)]
pub struct Revert {
/// The reason string, provided by the Solidity contract.
pub reason: String,
Expand All @@ -93,9 +93,16 @@ impl Borrow<str> for Revert {
}
}

impl Display for Revert {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "Revert: {}", self.reason)
impl fmt::Debug for Revert {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_tuple("Revert").field(&self.reason).finish()
}
}

impl fmt::Display for Revert {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("Revert: ")?;
f.write_str(&self.reason)
}
}

Expand All @@ -113,6 +120,15 @@ impl From<String> for Revert {
}
}

impl From<&str> for Revert {
#[inline]
fn from(value: &str) -> Self {
Self {
reason: value.into(),
}
}
}

impl SolError for Revert {
type Token = (PackedSeqToken,);
type Tuple = (crate::sol_data::String,);
Expand All @@ -137,58 +153,88 @@ impl SolError for Revert {
}
}

/// Represents a Solidity Panic. These are thrown by
/// `assert(condition, reason)` and by Solidity internal checks.
/// A [Solidity panic].
///
/// These are thrown by `assert(condition)` and by internal Solidity checks,
/// such as arithmetic overflow or array bounds checks.
///
/// [Solidity Panic](https://docs.soliditylang.org/en/v0.8.6/control-structures.html#panic-via-assert-and-error-via-require)
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
/// The list of all known panic codes can be found in the [PanicKind] enum.
///
/// [Solidity panic]: https://docs.soliditylang.org/en/latest/control-structures.html#panic-via-assert-and-error-via-require
#[derive(Clone, Copy, Default, PartialEq, Eq, Hash)]
pub struct Panic {
/// The panic code.
/// The [Solidity panic code].
///
/// [Solidity Panic Codes](https://docs.soliditylang.org/en/v0.8.6/control-structures.html#panic-via-assert-and-error-via-require)
pub error_code: U256,
/// [Solidity panic code]: https://docs.soliditylang.org/en/latest/control-structures.html#panic-via-assert-and-error-via-require
pub code: U256,
}

impl AsRef<U256> for Panic {
#[inline]
fn as_ref(&self) -> &U256 {
&self.error_code
&self.code
}
}

impl Borrow<U256> for Panic {
#[inline]
fn borrow(&self) -> &U256 {
&self.error_code
&self.code
}
}

impl fmt::Debug for Panic {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut debug = f.debug_tuple("Panic");
if let Some(kind) = self.kind() {
debug.field(&kind);
} else {
debug.field(&self.code);
}
debug.finish()
}
}

impl Display for Panic {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "Panic: {}", self.error_code)
impl fmt::Display for Panic {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("Panic: ")?;
if let Some(kind) = self.kind() {
f.write_str(kind.as_str())
} else {
write!(f, "unknown code: {}", self.code)
}
}
}

impl From<PanicKind> for Panic {
#[inline]
fn from(value: PanicKind) -> Self {
Self {
code: U256::from(value as u64),
}
}
}

impl From<u64> for Panic {
#[inline]
fn from(value: u64) -> Self {
Self {
error_code: U256::from(value),
code: U256::from(value),
}
}
}

impl From<Panic> for U256 {
#[inline]
fn from(value: Panic) -> Self {
value.error_code
value.code
}
}

impl From<U256> for Panic {
#[inline]
fn from(error_code: U256) -> Self {
Self { error_code }
fn from(value: U256) -> Self {
Self { code: value }
}
}

Expand All @@ -201,14 +247,12 @@ impl SolError for Panic {

#[inline]
fn to_rust(&self) -> <Self::Tuple as SolType>::RustType {
(self.error_code,)
(self.code,)
}

#[inline]
fn from_rust(tuple: <Self::Tuple as SolType>::RustType) -> Self {
Self {
error_code: tuple.0,
}
Self { code: tuple.0 }
}

#[inline]
Expand All @@ -217,14 +261,129 @@ impl SolError for Panic {
}
}

impl Panic {
/// Returns the [PanicKind] if this panic code is a known Solidity panic, as
/// described [in the Solidity documentation][ref].
///
/// [ref]: https://docs.soliditylang.org/en/latest/control-structures.html#panic-via-assert-and-error-via-require
pub fn kind(&self) -> Option<PanicKind> {
// use try_from to avoid copying by using the `&` impl
u32::try_from(&self.code)
.ok()
.and_then(PanicKind::from_number)
}
}

/// Represents a [Solidity panic].
/// Same as the [Solidity definition].
///
/// [Solidity panic]: https://docs.soliditylang.org/en/latest/control-structures.html#panic-via-assert-and-error-via-require
/// [Solidity definition]: https://github.com/ethereum/solidity/blob/9eaa5cebdb1458457135097efdca1a3573af17c8/libsolutil/ErrorCodes.h#L25-L37
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[repr(u32)]
pub enum PanicKind {
// Docs extracted from the Solidity definition and documentation, linked above.
/// Generic / unspecified error.
///
/// Generic compiler inserted panics.
#[default]
Generic = 0x00,
/// Used by the `assert()` builtin.
///
/// Thrown when you call `assert` with an argument that evaluates to
/// `false`.
Assert = 0x01,
/// Arithmetic underflow or overflow.
///
/// Thrown when an arithmetic operation results in underflow or overflow
/// outside of an `unchecked { ... }` block.
UnderOverflow = 0x11,
/// Division or modulo by zero.
///
/// Thrown when you divide or modulo by zero (e.g. `5 / 0` or `23 % 0`).
DivisionByZero = 0x12,
/// Enum conversion error.
///
/// Thrown when you convert a value that is too big or negative into an enum
/// type.
EnumConversionError = 0x21,
/// Invalid encoding in storage.
///
/// Thrown when you access a storage byte array that is incorrectly encoded.
StorageEncodingError = 0x22,
/// Empty array pop.
///
/// Thrown when you call `.pop()` on an empty array.
EmptyArrayPop = 0x31,
/// Array out of bounds access.
///
/// Thrown when you access an array, `bytesN` or an array slice at an
/// out-of-bounds or negative index (i.e. `x[i]` where `i >= x.length` or
/// `i < 0`).
ArrayOutOfBounds = 0x32,
/// Resource error (too large allocation or too large array).
///
/// Thrown when you allocate too much memory or create an array that is too
/// large.
ResourceError = 0x41,
/// Calling invalid internal function.
///
/// Thrown when you call a zero-initialized variable of internal function
/// type.
InvalidInternalFunction = 0x51,
}

impl fmt::Display for PanicKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}

impl PanicKind {
/// Returns the panic code for the given number if it is a known one.
pub const fn from_number(value: u32) -> Option<Self> {
match value {
0x00 => Some(Self::Generic),
0x01 => Some(Self::Assert),
0x11 => Some(Self::UnderOverflow),
0x12 => Some(Self::DivisionByZero),
0x21 => Some(Self::EnumConversionError),
0x22 => Some(Self::StorageEncodingError),
0x31 => Some(Self::EmptyArrayPop),
0x32 => Some(Self::ArrayOutOfBounds),
0x41 => Some(Self::ResourceError),
0x51 => Some(Self::InvalidInternalFunction),
_ => None,
}
}

/// Returns the panic code's string representation.
pub const fn as_str(self) -> &'static str {
// modified from the original Solidity comments:
// https://github.com/ethereum/solidity/blob/9eaa5cebdb1458457135097efdca1a3573af17c8/libsolutil/ErrorCodes.h#L25-L37
match self {
Self::Generic => "generic/unspecified error",
Self::Assert => "assertion failed",
Self::UnderOverflow => "arithmetic underflow or overflow",
Self::DivisionByZero => "division or modulo by zero",
Self::EnumConversionError => "failed to convert value into enum type",
Self::StorageEncodingError => "storage byte array incorrectly encoded",
Self::EmptyArrayPop => "called `.pop()` on an empty array",
Self::ArrayOutOfBounds => "array out-of-bounds access",
Self::ResourceError => "memory allocation error",
Self::InvalidInternalFunction => "called an invalid internal function",
}
}
}

#[cfg(test)]
mod test {
use super::*;
use ethers_primitives::keccak256;

#[test]
fn test_revert_encoding() {
let revert = Revert::from("test".to_string());
let revert = Revert::from("test");
let encoded = revert.encode();
let decoded = Revert::decode(&encoded, true).unwrap();
assert_eq!(encoded.len(), revert.data_size() + 4);
Expand All @@ -234,9 +393,8 @@ mod test {

#[test]
fn test_panic_encoding() {
let panic = Panic {
error_code: U256::from(0),
};
let panic = Panic { code: U256::ZERO };
assert_eq!(panic.kind(), Some(PanicKind::Generic));
let encoded = panic.encode();
let decoded = Panic::decode(&encoded, true).unwrap();

Expand Down
2 changes: 1 addition & 1 deletion crates/sol-types/src/types/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
mod error;
pub use error::{Panic, Revert, SolError};
pub use error::{Panic, PanicKind, Revert, SolError};

mod call;
pub use call::SolCall;
Expand Down

0 comments on commit 101fa06

Please sign in to comment.