Skip to content

Commit

Permalink
fix(dyn-abi): correctly parse empty lists of bytes (#548)
Browse files Browse the repository at this point in the history
* fix(dyn-abi): correctly parse empty lists of bytes

* test: bless tests

* chore: clippy

* chore: use FromHex
  • Loading branch information
DaniPopes authored Feb 29, 2024
1 parent e1c33ab commit 7e39882
Show file tree
Hide file tree
Showing 8 changed files with 72 additions and 35 deletions.
50 changes: 40 additions & 10 deletions crates/dyn-abi/src/coerce.rs
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,7 @@ enum Error {
TooManyDecimals(usize, usize),
InvalidFixedBytesLength(usize),
FixedArrayLengthMismatch(usize, usize),
EmptyHexStringWithoutPrefix,
}

#[cfg(feature = "std")]
Expand Down Expand Up @@ -284,6 +285,7 @@ impl fmt::Display for Error {
f,
"fixed array length mismatch: expected {expected} elements, got {actual}"
),
Self::EmptyHexStringWithoutPrefix => f.write_str("expected hex digits or the `0x` prefix for an empty hex string"),
}
}
}
Expand Down Expand Up @@ -474,27 +476,35 @@ fn fixed_bytes<'i>(len: usize) -> impl Parser<&'i str, Word, ContextError> {

#[inline]
fn address(input: &mut &str) -> PResult<Address> {
trace("address", fixed_bytes_inner).parse_next(input).map(Address::from)
trace("address", hex_str.try_map(hex::FromHex::from_hex)).parse_next(input)
}

#[inline]
fn function(input: &mut &str) -> PResult<Function> {
trace("function", fixed_bytes_inner).parse_next(input).map(Function::from)
trace("function", hex_str.try_map(hex::FromHex::from_hex)).parse_next(input)
}

#[inline]
fn bytes(input: &mut &str) -> PResult<Vec<u8>> {
trace("bytes", hex_str.try_map(hex::decode)).parse_next(input)
}

#[inline]
fn fixed_bytes_inner<const N: usize>(input: &mut &str) -> PResult<FixedBytes<N>> {
hex_str.try_map(|s| hex::decode_to_array(s).map(Into::into)).parse_next(input)
}

#[inline]
fn hex_str<'i>(input: &mut &'i str) -> PResult<&'i str> {
trace("hex_str", preceded(opt("0x"), hex_digit0)).parse_next(input)
trace("hex_str", |input: &mut &'i str| {
// Allow empty `bytes` only with a prefix.
let has_prefix = opt("0x").parse_next(input)?.is_some();
let s = hex_digit0(input)?;
if !has_prefix && s.is_empty() {
return Err(ErrMode::from_external_error(
input,
ErrorKind::Verify,
Error::EmptyHexStringWithoutPrefix,
));
}
Ok(s)
})
.parse_next(input)
}

fn hex_error(input: &&str, e: FromHexError) -> ErrMode<ContextError> {
Expand Down Expand Up @@ -849,7 +859,7 @@ mod tests {
);

let e = DynSolType::FixedBytes(1).coerce_str("").unwrap_err();
assert_error_contains(&e, "Invalid string length");
assert_error_contains(&e, &Error::EmptyHexStringWithoutPrefix.to_string());
let e = DynSolType::FixedBytes(1).coerce_str("0").unwrap_err();
assert_error_contains(&e, "Odd number of digits");
let e = DynSolType::FixedBytes(1).coerce_str("0x").unwrap_err();
Expand Down Expand Up @@ -919,7 +929,9 @@ mod tests {

#[test]
fn coerce_bytes() {
assert_eq!(DynSolType::Bytes.coerce_str("").unwrap(), DynSolValue::Bytes(vec![]));
let e = DynSolType::Bytes.coerce_str("").unwrap_err();
assert_error_contains(&e, &Error::EmptyHexStringWithoutPrefix.to_string());

assert_eq!(DynSolType::Bytes.coerce_str("0x").unwrap(), DynSolValue::Bytes(vec![]));
assert!(DynSolType::Bytes.coerce_str("0x0").is_err());
assert!(DynSolType::Bytes.coerce_str("0").is_err());
Expand Down Expand Up @@ -1038,6 +1050,24 @@ mod tests {
assert_eq!(arr.coerce_str("[\"\", \"\"]").unwrap(), mk_arr(&["", ""]));
}

#[test]
fn coerce_array_of_bytes_and_strings() {
let ty = DynSolType::Array(Box::new(DynSolType::Bytes));
assert_eq!(ty.coerce_str("[]"), Ok(DynSolValue::Array(vec![])));
assert_eq!(ty.coerce_str("[0x]"), Ok(DynSolValue::Array(vec![DynSolValue::Bytes(vec![])])));

let ty = DynSolType::Array(Box::new(DynSolType::String));
assert_eq!(ty.coerce_str("[]"), Ok(DynSolValue::Array(vec![])));
assert_eq!(
ty.coerce_str("[\"\"]"),
Ok(DynSolValue::Array(vec![DynSolValue::String(String::new())]))
);
assert_eq!(
ty.coerce_str("[0x]"),
Ok(DynSolValue::Array(vec![DynSolValue::String("0x".into())]))
);
}

#[test]
fn coerce_empty_array() {
assert_eq!(
Expand Down
4 changes: 2 additions & 2 deletions crates/json-abi/src/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -427,8 +427,8 @@ mod tests {
fn test_item_parse() {
assert_eq!(parse_sig::<true>("foo()"), Ok(("foo".into(), vec![], vec![], false)));
assert_eq!(parse_sig::<true>("foo()()"), Ok(("foo".into(), vec![], vec![], false)));
assert_eq!(parse_sig::<true>("foo(,) \t ()"), Ok(("foo".into(), vec![], vec![], false)));
assert_eq!(parse_sig::<true>("foo(,) (,)"), Ok(("foo".into(), vec![], vec![], false)));
assert_eq!(parse_sig::<true>("foo() \t ()"), Ok(("foo".into(), vec![], vec![], false)));
assert_eq!(parse_sig::<true>("foo() ()"), Ok(("foo".into(), vec![], vec![], false)));

assert_eq!(parse_sig::<false>("foo()"), Ok(("foo".into(), vec![], vec![], false)));
parse_sig::<false>("foo()()").unwrap_err();
Expand Down
5 changes: 2 additions & 3 deletions crates/primitives/src/bits/fixed.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use crate::aliases;
use core::{fmt, iter, ops, str};
use derive_more::{Deref, DerefMut, From, Index, IndexMut, IntoIterator};
use hex::FromHex;

/// A byte array of fixed length (`[u8; N]`).
///
Expand Down Expand Up @@ -307,9 +308,7 @@ impl<const N: usize> str::FromStr for FixedBytes<N> {

#[inline]
fn from_str(s: &str) -> Result<Self, Self::Err> {
let mut buf = [0u8; N];
hex::decode_to_slice(s, &mut buf)?;
Ok(Self(buf))
Self::from_hex(s)
}
}

Expand Down
9 changes: 2 additions & 7 deletions crates/primitives/src/bits/macros.rs
Original file line number Diff line number Diff line change
Expand Up @@ -414,13 +414,8 @@ macro_rules! impl_fb_traits {
type Error = $crate::hex::FromHexError;

#[inline]
fn from_hex<T>(hex: T) -> Result<Self, Self::Error>
where
T: $crate::private::AsRef<[u8]>
{
let mut buf = [0u8; $n];
$crate::hex::decode_to_slice(hex.as_ref(), &mut buf)?;
Ok(Self::new(buf))
fn from_hex<T: $crate::private::AsRef<[u8]>>(hex: T) -> Result<Self, Self::Error> {
$crate::hex::decode_to_array(hex).map(Self::new)
}
}
};
Expand Down
6 changes: 3 additions & 3 deletions crates/sol-type-parser/src/parameter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -206,9 +206,9 @@ mod tests {
#[test]
fn parse_params() {
assert_eq!(Parameters::parse("()"), Ok(Parameters { span: "()", params: vec![] }));
assert_eq!(Parameters::parse("(,)"), Ok(Parameters { span: "(,)", params: vec![] }));
assert_eq!(Parameters::parse("(, )"), Ok(Parameters { span: "(, )", params: vec![] }));
assert_eq!(Parameters::parse("( , )"), Ok(Parameters { span: "( , )", params: vec![] }));
assert_eq!(Parameters::parse("( )"), Ok(Parameters { span: "( )", params: vec![] }));
assert_eq!(Parameters::parse("( )"), Ok(Parameters { span: "( )", params: vec![] }));
assert_eq!(Parameters::parse("( )"), Ok(Parameters { span: "( )", params: vec![] }));

assert_eq!(
Parameters::parse("(\tuint256 , \t)"),
Expand Down
23 changes: 14 additions & 9 deletions crates/sol-type-parser/src/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use alloc::{string::String, vec::Vec};
use core::{slice, str};
use winnow::{
ascii::space0,
combinator::{cut_err, delimited, opt, preceded, separated, terminated, trace},
combinator::{cut_err, opt, preceded, separated, terminated, trace},
error::{AddContext, ParserError, StrContext, StrContextValue},
stream::Accumulate,
PResult, Parser,
Expand Down Expand Up @@ -88,14 +88,19 @@ where
let name = format!("list({open:?}, {delim:?}, {close:?})");
#[cfg(not(feature = "debug"))]
let name = "list";
trace(
name,
delimited(
(char_parser(open), space0),
separated(0.., f, (char_parser(delim), space0)),
(opt(delim), space0, cut_err(char_parser(close))),
),
)

// These have to be outside of the closure for some reason.
let elems_1 = separated(1.., f, (char_parser(delim), space0));
let mut elems_and_end = terminated(elems_1, (opt(delim), space0, cut_err(char_parser(close))));
trace(name, move |input: &mut &'i str| {
let _ = char_parser(open).parse_next(input)?;
let _ = space0(input)?;
if let Some(stripped) = input.strip_prefix(close) {
*input = stripped;
return Ok(O2::initial(Some(0)));
}
elems_and_end.parse_next(input)
})
}

pub fn opt_ws_ident<'a>(input: &mut &'a str) -> PResult<Option<&'a str>> {
Expand Down
2 changes: 1 addition & 1 deletion crates/sol-types/src/abi/encoder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -706,7 +706,7 @@ mod tests {
"
);

let data = (5, bytes.clone(), 3, bytes);
let data = (5, bytes, 3, bytes);

let encoded = MyTy::abi_encode(&data);
let encoded_params = MyTy::abi_encode_params(&data);
Expand Down
8 changes: 8 additions & 0 deletions scripts/bless_tests.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
#!/usr/bin/env bash

set -eo pipefail

export RUSTFLAGS="-Zthreads=1"
export TRYBUILD=overwrite
cargo +nightly test -p alloy-sol-types --test compiletest
cargo +nightly test -p alloy-sol-types --test compiletest --all-features

0 comments on commit 7e39882

Please sign in to comment.