Skip to content

Commit 7e39882

Browse files
authored
fix(dyn-abi): correctly parse empty lists of bytes (#548)
* fix(dyn-abi): correctly parse empty lists of bytes * test: bless tests * chore: clippy * chore: use FromHex
1 parent e1c33ab commit 7e39882

File tree

8 files changed

+72
-35
lines changed

8 files changed

+72
-35
lines changed

crates/dyn-abi/src/coerce.rs

Lines changed: 40 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -255,6 +255,7 @@ enum Error {
255255
TooManyDecimals(usize, usize),
256256
InvalidFixedBytesLength(usize),
257257
FixedArrayLengthMismatch(usize, usize),
258+
EmptyHexStringWithoutPrefix,
258259
}
259260

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

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

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

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

490-
#[inline]
491-
fn fixed_bytes_inner<const N: usize>(input: &mut &str) -> PResult<FixedBytes<N>> {
492-
hex_str.try_map(|s| hex::decode_to_array(s).map(Into::into)).parse_next(input)
493-
}
494-
495492
#[inline]
496493
fn hex_str<'i>(input: &mut &'i str) -> PResult<&'i str> {
497-
trace("hex_str", preceded(opt("0x"), hex_digit0)).parse_next(input)
494+
trace("hex_str", |input: &mut &'i str| {
495+
// Allow empty `bytes` only with a prefix.
496+
let has_prefix = opt("0x").parse_next(input)?.is_some();
497+
let s = hex_digit0(input)?;
498+
if !has_prefix && s.is_empty() {
499+
return Err(ErrMode::from_external_error(
500+
input,
501+
ErrorKind::Verify,
502+
Error::EmptyHexStringWithoutPrefix,
503+
));
504+
}
505+
Ok(s)
506+
})
507+
.parse_next(input)
498508
}
499509

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

851861
let e = DynSolType::FixedBytes(1).coerce_str("").unwrap_err();
852-
assert_error_contains(&e, "Invalid string length");
862+
assert_error_contains(&e, &Error::EmptyHexStringWithoutPrefix.to_string());
853863
let e = DynSolType::FixedBytes(1).coerce_str("0").unwrap_err();
854864
assert_error_contains(&e, "Odd number of digits");
855865
let e = DynSolType::FixedBytes(1).coerce_str("0x").unwrap_err();
@@ -919,7 +929,9 @@ mod tests {
919929

920930
#[test]
921931
fn coerce_bytes() {
922-
assert_eq!(DynSolType::Bytes.coerce_str("").unwrap(), DynSolValue::Bytes(vec![]));
932+
let e = DynSolType::Bytes.coerce_str("").unwrap_err();
933+
assert_error_contains(&e, &Error::EmptyHexStringWithoutPrefix.to_string());
934+
923935
assert_eq!(DynSolType::Bytes.coerce_str("0x").unwrap(), DynSolValue::Bytes(vec![]));
924936
assert!(DynSolType::Bytes.coerce_str("0x0").is_err());
925937
assert!(DynSolType::Bytes.coerce_str("0").is_err());
@@ -1038,6 +1050,24 @@ mod tests {
10381050
assert_eq!(arr.coerce_str("[\"\", \"\"]").unwrap(), mk_arr(&["", ""]));
10391051
}
10401052

1053+
#[test]
1054+
fn coerce_array_of_bytes_and_strings() {
1055+
let ty = DynSolType::Array(Box::new(DynSolType::Bytes));
1056+
assert_eq!(ty.coerce_str("[]"), Ok(DynSolValue::Array(vec![])));
1057+
assert_eq!(ty.coerce_str("[0x]"), Ok(DynSolValue::Array(vec![DynSolValue::Bytes(vec![])])));
1058+
1059+
let ty = DynSolType::Array(Box::new(DynSolType::String));
1060+
assert_eq!(ty.coerce_str("[]"), Ok(DynSolValue::Array(vec![])));
1061+
assert_eq!(
1062+
ty.coerce_str("[\"\"]"),
1063+
Ok(DynSolValue::Array(vec![DynSolValue::String(String::new())]))
1064+
);
1065+
assert_eq!(
1066+
ty.coerce_str("[0x]"),
1067+
Ok(DynSolValue::Array(vec![DynSolValue::String("0x".into())]))
1068+
);
1069+
}
1070+
10411071
#[test]
10421072
fn coerce_empty_array() {
10431073
assert_eq!(

crates/json-abi/src/utils.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -427,8 +427,8 @@ mod tests {
427427
fn test_item_parse() {
428428
assert_eq!(parse_sig::<true>("foo()"), Ok(("foo".into(), vec![], vec![], false)));
429429
assert_eq!(parse_sig::<true>("foo()()"), Ok(("foo".into(), vec![], vec![], false)));
430-
assert_eq!(parse_sig::<true>("foo(,) \t ()"), Ok(("foo".into(), vec![], vec![], false)));
431-
assert_eq!(parse_sig::<true>("foo(,) (,)"), Ok(("foo".into(), vec![], vec![], false)));
430+
assert_eq!(parse_sig::<true>("foo() \t ()"), Ok(("foo".into(), vec![], vec![], false)));
431+
assert_eq!(parse_sig::<true>("foo() ()"), Ok(("foo".into(), vec![], vec![], false)));
432432

433433
assert_eq!(parse_sig::<false>("foo()"), Ok(("foo".into(), vec![], vec![], false)));
434434
parse_sig::<false>("foo()()").unwrap_err();

crates/primitives/src/bits/fixed.rs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use crate::aliases;
22
use core::{fmt, iter, ops, str};
33
use derive_more::{Deref, DerefMut, From, Index, IndexMut, IntoIterator};
4+
use hex::FromHex;
45

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

308309
#[inline]
309310
fn from_str(s: &str) -> Result<Self, Self::Err> {
310-
let mut buf = [0u8; N];
311-
hex::decode_to_slice(s, &mut buf)?;
312-
Ok(Self(buf))
311+
Self::from_hex(s)
313312
}
314313
}
315314

crates/primitives/src/bits/macros.rs

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -414,13 +414,8 @@ macro_rules! impl_fb_traits {
414414
type Error = $crate::hex::FromHexError;
415415

416416
#[inline]
417-
fn from_hex<T>(hex: T) -> Result<Self, Self::Error>
418-
where
419-
T: $crate::private::AsRef<[u8]>
420-
{
421-
let mut buf = [0u8; $n];
422-
$crate::hex::decode_to_slice(hex.as_ref(), &mut buf)?;
423-
Ok(Self::new(buf))
417+
fn from_hex<T: $crate::private::AsRef<[u8]>>(hex: T) -> Result<Self, Self::Error> {
418+
$crate::hex::decode_to_array(hex).map(Self::new)
424419
}
425420
}
426421
};

crates/sol-type-parser/src/parameter.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -206,9 +206,9 @@ mod tests {
206206
#[test]
207207
fn parse_params() {
208208
assert_eq!(Parameters::parse("()"), Ok(Parameters { span: "()", params: vec![] }));
209-
assert_eq!(Parameters::parse("(,)"), Ok(Parameters { span: "(,)", params: vec![] }));
210-
assert_eq!(Parameters::parse("(, )"), Ok(Parameters { span: "(, )", params: vec![] }));
211-
assert_eq!(Parameters::parse("( , )"), Ok(Parameters { span: "( , )", params: vec![] }));
209+
assert_eq!(Parameters::parse("( )"), Ok(Parameters { span: "( )", params: vec![] }));
210+
assert_eq!(Parameters::parse("( )"), Ok(Parameters { span: "( )", params: vec![] }));
211+
assert_eq!(Parameters::parse("( )"), Ok(Parameters { span: "( )", params: vec![] }));
212212

213213
assert_eq!(
214214
Parameters::parse("(\tuint256 , \t)"),

crates/sol-type-parser/src/utils.rs

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ use alloc::{string::String, vec::Vec};
55
use core::{slice, str};
66
use winnow::{
77
ascii::space0,
8-
combinator::{cut_err, delimited, opt, preceded, separated, terminated, trace},
8+
combinator::{cut_err, opt, preceded, separated, terminated, trace},
99
error::{AddContext, ParserError, StrContext, StrContextValue},
1010
stream::Accumulate,
1111
PResult, Parser,
@@ -88,14 +88,19 @@ where
8888
let name = format!("list({open:?}, {delim:?}, {close:?})");
8989
#[cfg(not(feature = "debug"))]
9090
let name = "list";
91-
trace(
92-
name,
93-
delimited(
94-
(char_parser(open), space0),
95-
separated(0.., f, (char_parser(delim), space0)),
96-
(opt(delim), space0, cut_err(char_parser(close))),
97-
),
98-
)
91+
92+
// These have to be outside of the closure for some reason.
93+
let elems_1 = separated(1.., f, (char_parser(delim), space0));
94+
let mut elems_and_end = terminated(elems_1, (opt(delim), space0, cut_err(char_parser(close))));
95+
trace(name, move |input: &mut &'i str| {
96+
let _ = char_parser(open).parse_next(input)?;
97+
let _ = space0(input)?;
98+
if let Some(stripped) = input.strip_prefix(close) {
99+
*input = stripped;
100+
return Ok(O2::initial(Some(0)));
101+
}
102+
elems_and_end.parse_next(input)
103+
})
99104
}
100105

101106
pub fn opt_ws_ident<'a>(input: &mut &'a str) -> PResult<Option<&'a str>> {

crates/sol-types/src/abi/encoder.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -706,7 +706,7 @@ mod tests {
706706
"
707707
);
708708

709-
let data = (5, bytes.clone(), 3, bytes);
709+
let data = (5, bytes, 3, bytes);
710710

711711
let encoded = MyTy::abi_encode(&data);
712712
let encoded_params = MyTy::abi_encode_params(&data);

scripts/bless_tests.sh

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
#!/usr/bin/env bash
2+
3+
set -eo pipefail
4+
5+
export RUSTFLAGS="-Zthreads=1"
6+
export TRYBUILD=overwrite
7+
cargo +nightly test -p alloy-sol-types --test compiletest
8+
cargo +nightly test -p alloy-sol-types --test compiletest --all-features

0 commit comments

Comments
 (0)