Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Prestwich/dyn sol error #551

Merged
merged 10 commits into from
Mar 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion crates/dyn-abi/src/arbitrary.rs
Original file line number Diff line number Diff line change
Expand Up @@ -723,7 +723,9 @@ mod tests {
match value {
DynSolValue::Array(t)
| DynSolValue::FixedArray(t)
| crate::ty::as_tuple!(DynSolValue t) => t.iter().try_for_each(assert_valid_value)?,
| crate::dynamic::ty::as_tuple!(DynSolValue t) => {
t.iter().try_for_each(assert_valid_value)?
}
_ => {}
}

Expand Down
2 changes: 1 addition & 1 deletion crates/dyn-abi/src/coerce.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::{ty::as_tuple, DynSolType, DynSolValue, Result};
use crate::{dynamic::ty::as_tuple, DynSolType, DynSolValue, Result};
use alloc::vec::Vec;
use alloy_primitives::{Address, FixedBytes, Function, Sign, I256, U256};
use alloy_sol_types::Word;
Expand Down
125 changes: 125 additions & 0 deletions crates/dyn-abi/src/dynamic/error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
use crate::{DynSolType, DynSolValue, Error, Result};
use alloc::vec::Vec;
use alloy_primitives::{keccak256, Selector};
use alloy_sol_types::SolError;

/// See [alloy_sol_types::Panic] for signature details.
const PANIC_SELECTOR: Selector = Selector::new(alloy_sol_types::Panic::SELECTOR);
/// See [alloy_sol_types::Revert] for signature details.
const REVERT_SELECTOR: Selector = Selector::new(alloy_sol_types::Revert::SELECTOR);

/// A dynamic ABI error.
///
/// This is a representation of a Solidity error, which can be used to decode
/// error events.
#[derive(Debug, Clone, PartialEq)]
pub struct DynSolError {
/// Error selector.
pub(crate) selector: Selector,
/// Error body types. MUST be a tuple.
pub(crate) body: DynSolType,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Vec<DynSolType>?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

DynSolEvent uses DynSolType for body. could change both?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i'd rather leave as-is for now

}

impl DynSolError {
/// Represents a standard Solidity revert. These are thrown by
/// `revert(reason)` or `require(condition, reason)` statements in Solidity.
///
/// **Note**: Usage of this instantiator is not recommended. It is better to
/// use [alloy_sol_types::Revert] in almost all cases.
pub fn revert() -> Self {
Self { selector: REVERT_SELECTOR, body: DynSolType::Tuple(vec![DynSolType::String]) }
}

/// A [Solidity panic].
///
/// **Note**: Usage of this instantiator is not recommended. It is better to
/// use [alloy_sol_types::Panic] in almost all cases.
///
/// These are thrown by `assert(condition)` and by internal Solidity checks,
/// such as arithmetic overflow or array bounds checks.
///
/// 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
/// [PanicKind]: alloy_sol_types::PanicKind
pub fn panic() -> Self {
Self { selector: PANIC_SELECTOR, body: DynSolType::Tuple(vec![DynSolType::Uint(256)]) }
}

/// Creates a new error, without length-checking the body. This allows
/// creation of invalid errors.
pub const fn new_unchecked(selector: Selector, body: DynSolType) -> Self {
Self { selector, body }
}

/// Creates a new error from a selector.
pub fn new(selector: Selector, body: DynSolType) -> Option<Self> {
let _ = body.as_tuple()?;
Some(Self::new_unchecked(selector, body))
}

/// Error selector is the first 4 bytes of the keccak256 hash of the error
/// declaration.
pub const fn selector(&self) -> Selector {
self.selector
}

prestwich marked this conversation as resolved.
Show resolved Hide resolved
/// Error body types.
pub fn body(&self) -> &[DynSolType] {
self.body.as_tuple().expect("body is a tuple")
}

/// Decode the error from the given data, which must already be stripped of
/// its selector.
fn decode_error_body(&self, data: &[u8]) -> Result<DecodedError> {
let body = self.body.abi_decode_sequence(data)?.into_fixed_seq().expect("body is a tuple");
Ok(DecodedError { body })
}

/// Decode the error from the given data.
pub fn decode_error(&self, data: &[u8]) -> Result<DecodedError> {
// Check selector validity.
if !data.starts_with(self.selector.as_slice()) {
return Err(Error::SelectorMismatch {
expected: self.selector,
actual: Selector::from_slice(&data[0..4]),
});
}

// will not panic, as we've already checked the length with starts_with
let data = data.split_at(4).1;
self.decode_error_body(data)
}
}

/// A decoded dynamic ABI error.
#[derive(Debug, Clone, PartialEq)]
pub struct DecodedError {
/// The decoded error body.
pub body: Vec<DynSolValue>,
}

#[cfg(test)]
mod test {
use super::DynSolError;
use crate::DynSolValue;
use alloy_primitives::hex;

#[test]
fn decode_revert_message() {
let error = DynSolError::revert();
let data = hex!("08c379a0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000042020202000000000000000000000000000000000000000000000000000000000");

let decoded = error.decode_error(&data).unwrap();
assert_eq!(decoded.body, vec!(DynSolValue::String(" ".into())));
}

#[test]
fn decode_panic() {
let error = DynSolError::panic();
let data = hex!("4e487b710000000000000000000000000000000000000000000000000000000000000001");

let decoded = error.decode_error(&data).unwrap();
assert_eq!(decoded.body, vec![DynSolValue::Uint(alloy_primitives::Uint::from(1), 256)]);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@ impl DynSolEvent {
/// Checks that the indexed length is less than or equal to 4, and that the
/// body is a tuple.
pub fn new(topic_0: Option<B256>, indexed: Vec<DynSolType>, body: DynSolType) -> Option<Self> {
if indexed.len() > 4 || body.as_tuple().is_none() {
let topics = indexed.len() + topic_0.is_some() as usize;
if topics > 4 || body.as_tuple().is_none() {
return None;
}
Some(Self::new_unchecked(topic_0, indexed, body))
Expand Down
14 changes: 14 additions & 0 deletions crates/dyn-abi/src/dynamic/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
mod error;
pub use error::{DecodedError, DynSolError};

mod event;
pub use event::{DecodedEvent, DynSolEvent};

pub(crate) mod ty;
pub use ty::DynSolType;

mod token;
pub use token::DynToken;

mod value;
pub use value::DynSolValue;
File renamed without changes.
File renamed without changes.
File renamed without changes.
15 changes: 13 additions & 2 deletions crates/dyn-abi/src/error.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use alloc::{borrow::Cow, string::String};
use alloy_primitives::B256;
use alloy_primitives::{Selector, B256};
use alloy_sol_types::Error as SolTypesError;
use core::fmt;
use hex::FromHexError;
Expand Down Expand Up @@ -45,6 +45,15 @@ pub enum Error {
/// The actual length.
actual: usize,
},

/// Selector mismatch during function or error decoding.
SelectorMismatch {
/// The expected selector.
expected: Selector,
/// The actual selector.
actual: Selector,
},

/// Invalid event signature.
EventSignatureMismatch {
/// The expected signature.
Expand Down Expand Up @@ -126,7 +135,9 @@ impl fmt::Display for Error {
Self::EventSignatureMismatch { expected, actual } => {
write!(f, "invalid event signature: expected {expected}, got {actual}",)
}

Self::SelectorMismatch { expected, actual } => {
write!(f, "selector mismatch: expected {expected}, got {actual}",)
}
Self::Hex(e) => e.fmt(f),
Self::TypeParser(e) => e.fmt(f),
Self::SolTypes(e) => e.fmt(f),
Expand Down
36 changes: 36 additions & 0 deletions crates/dyn-abi/src/ext/error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
use crate::{DynSolError, Specifier};
use alloc::vec::Vec;
use alloy_json_abi::Error;
use alloy_primitives::{keccak256, Selector};

mod sealed {
pub trait Sealed {}
impl Sealed for alloy_json_abi::Error {}
}
use sealed::Sealed;

impl Specifier<DynSolError> for Error {
fn resolve(&self) -> crate::Result<DynSolError> {
let signature = self.signature();
let selector = Selector::from_slice(&keccak256(signature)[0..4]);

let mut body = Vec::with_capacity(self.inputs.len());
for param in &self.inputs {
body.push(param.resolve()?);
}

Ok(DynSolError::new_unchecked(selector, crate::DynSolType::Tuple(body)))
}
}

/// Provides error encoding and decoding for the [`Error`] type.
pub trait ErrorExt: sealed::Sealed {
/// Decode the error from the given data.
fn decode_error(&self, data: &[u8]) -> crate::Result<crate::DecodedError>;
}

impl ErrorExt for alloy_json_abi::Error {
fn decode_error(&self, data: &[u8]) -> crate::Result<crate::DecodedError> {
self.resolve()?.decode_error(data)
}
}
2 changes: 1 addition & 1 deletion crates/dyn-abi/src/ext/event.rs
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ impl EventExt for Event {
where
I: IntoIterator<Item = B256>,
{
Specifier::resolve(self)?.decode_log_parts(topics, data, validate)
self.resolve()?.decode_log_parts(topics, data, validate)
}
}

Expand Down
3 changes: 3 additions & 0 deletions crates/dyn-abi/src/ext/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,6 @@ pub use abi::{FunctionExt, JsonAbiExt};

mod event;
pub use event::EventExt;

mod error;
pub use error::ErrorExt;
23 changes: 8 additions & 15 deletions crates/dyn-abi/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,26 +36,19 @@ mod arbitrary;

mod coerce;

mod dynamic;
pub use dynamic::{
DecodedError, DecodedEvent, DynSolError, DynSolEvent, DynSolType, DynSolValue, DynToken,
};

mod error;
pub use error::{Error, Result};

mod ext;
pub use ext::{EventExt, FunctionExt, JsonAbiExt};

mod event;
pub use event::{DecodedEvent, DynSolEvent};

mod ty;
pub use ty::DynSolType;

mod value;
pub use value::DynSolValue;

mod token;
pub use token::DynToken;
pub use ext::{ErrorExt, EventExt, FunctionExt, JsonAbiExt};

mod resolve;
pub use resolve::Specifier;
mod specifier;
pub use specifier::Specifier;

#[cfg(feature = "eip712")]
pub mod eip712;
Expand Down
File renamed without changes.