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

feat(dyn-abi): support parse scientific number #835

Merged
merged 7 commits into from
Jan 1, 2025
Merged
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
145 changes: 104 additions & 41 deletions crates/dyn-abi/src/coerce.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ use winnow::{
ascii::{alpha0, alpha1, digit1, hex_digit0, hex_digit1, space0},
combinator::{cut_err, dispatch, empty, fail, opt, preceded, trace},
error::{
AddContext, ContextError, ErrMode, ErrorKind, FromExternalError, StrContext,
AddContext, ContextError, ErrMode, ErrorKind, FromExternalError, ParserError, StrContext,
StrContextValue,
},
stream::Stream,
Expand Down Expand Up @@ -250,6 +250,7 @@ impl<'a> ValueParser<'a> {
enum Error {
IntOverflow,
FractionalNotAllowed(U256),
NegativeUnits,
TooManyDecimals(usize, usize),
InvalidFixedBytesLength(usize),
FixedArrayLengthMismatch(usize, usize),
Expand All @@ -265,18 +266,17 @@ impl fmt::Display for Error {
Self::TooManyDecimals(expected, actual) => {
write!(f, "expected at most {expected} decimals, got {actual}")
}
Self::FractionalNotAllowed(n) => write!(
f,
"non-zero fraction 0.{n} not allowed without specifying non-wei units (gwei, ether, etc.)"
),
Self::FractionalNotAllowed(n) => write!(f, "non-zero fraction .{n} not allowed"),
Self::NegativeUnits => f.write_str("negative units not allowed"),
Self::InvalidFixedBytesLength(len) => {
write!(f, "fixed bytes length {len} greater than 32")
}
Self::FixedArrayLengthMismatch(expected, actual) => write!(
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"),
Self::FixedArrayLengthMismatch(expected, actual) => {
write!(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 @@ -335,20 +335,34 @@ fn uint<'i>(len: usize) -> impl Parser<Input<'i>, U256, ContextError> {
#[cfg(not(feature = "debug"))]
let name = "uint";
trace(name, move |input: &mut Input<'_>| {
let (s, (intpart, fract)) = spanned((
prefixed_int,
let intpart = prefixed_int(input)?;
let fract =
opt(preceded(
'.',
cut_err(digit1.context(StrContext::Expected(StrContextValue::Description(
"at least one digit",
)))),
)),
))
.parse_next(input)?;
))
.parse_next(input)?;

let intpart = intpart
.parse::<U256>()
.map_err(|e| ErrMode::from_external_error(input, ErrorKind::Verify, e))?;
let e = opt(scientific_notation).parse_next(input)?.unwrap_or(0);

let _ = space0(input)?;
let units = int_units(input)?;

let units = units as isize + e;
if units < 0 {
return Err(ErrMode::from_external_error(
input,
ErrorKind::Verify,
Error::NegativeUnits,
));
}
let units = units as usize;

let uint = if let Some(fract) = fract {
let fract_uint = U256::from_str_radix(fract, 10)
.map_err(|e| ErrMode::from_external_error(input, ErrorKind::Verify, e))?;
Expand All @@ -370,21 +384,19 @@ fn uint<'i>(len: usize) -> impl Parser<Input<'i>, U256, ContextError> {
}

// (intpart * 10^fract.len() + fract) * 10^(units-fract.len())
U256::from_str_radix(intpart, 10)
.map_err(|e| ErrMode::from_external_error(input, ErrorKind::Verify, e))?
intpart
.checked_mul(U256::from(10usize.pow(fract.len() as u32)))
.and_then(|u| u.checked_add(fract_uint))
.and_then(|u| u.checked_mul(U256::from(10usize.pow((units - fract.len()) as u32))))
.ok_or_else(|| {
ErrMode::from_external_error(input, ErrorKind::Verify, Error::IntOverflow)
})
} else if units > 0 {
intpart.checked_mul(U256::from(10usize.pow(units as u32))).ok_or_else(|| {
ErrMode::from_external_error(input, ErrorKind::Verify, Error::IntOverflow)
})
} else {
s.parse::<U256>()
.map_err(|e| ErrMode::from_external_error(input, ErrorKind::Verify, e))?
.checked_mul(U256::from(10usize.pow(units as u32)))
.ok_or_else(|| {
ErrMode::from_external_error(input, ErrorKind::Verify, Error::IntOverflow)
})
Ok(intpart)
}?;

if uint.bit_len() > len {
Expand All @@ -397,25 +409,30 @@ fn uint<'i>(len: usize) -> impl Parser<Input<'i>, U256, ContextError> {

#[inline]
fn prefixed_int<'i>(input: &mut Input<'i>) -> PResult<&'i str> {
trace("prefixed_int", |input: &mut Input<'i>| {
let has_prefix = matches!(input.get(..2), Some("0b" | "0B" | "0o" | "0O" | "0x" | "0X"));
let checkpoint = input.checkpoint();
if has_prefix {
let _ = input.next_slice(2);
// parse hex since it's the most general
hex_digit1(input)
} else {
digit1(input)
}
.map_err(|e| {
e.add_context(
input,
&checkpoint,
StrContext::Expected(StrContextValue::Description("at least one digit")),
)
})
})
trace(
"prefixed_int",
spanned(|input: &mut Input<'i>| {
let has_prefix =
matches!(input.get(..2), Some("0b" | "0B" | "0o" | "0O" | "0x" | "0X"));
let checkpoint = input.checkpoint();
if has_prefix {
let _ = input.next_slice(2);
// parse hex since it's the most general
hex_digit1(input)
} else {
digit1(input)
}
.map_err(|e| {
e.add_context(
input,
&checkpoint,
StrContext::Expected(StrContextValue::Description("at least one digit")),
)
})
}),
)
.parse_next(input)
.map(|(s, _)| s)
}

#[inline]
Expand All @@ -432,6 +449,16 @@ fn int_units(input: &mut Input<'_>) -> PResult<usize> {
.parse_next(input)
}

#[inline]
fn scientific_notation(input: &mut Input<'_>) -> PResult<isize> {
// Check if we have 'e' or 'E' followed by an optional sign and digits
if !matches!(input.chars().next(), Some('e' | 'E')) {
return Err(ErrMode::from_error_kind(input, ErrorKind::Fail));
};
let _ = input.next_token();
winnow::ascii::dec_int(input)
}

#[inline]
fn fixed_bytes<'i>(len: usize) -> impl Parser<Input<'i>, Word, ContextError> {
#[cfg(feature = "debug")]
Expand Down Expand Up @@ -1283,4 +1310,40 @@ mod tests {
}
assert_eq!(value, DynSolValue::Bool(true));
}

#[test]
fn coerce_uint_scientific() {
assert_eq!(
DynSolType::Uint(256).coerce_str("1e18").unwrap(),
DynSolValue::Uint(U256::from_str("1000000000000000000").unwrap(), 256)
);

assert_eq!(
DynSolType::Uint(256).coerce_str("74258.225772486694040708e18").unwrap(),
DynSolValue::Uint(U256::from_str("74258225772486694040708").unwrap(), 256)
);

assert_eq!(
DynSolType::Uint(256).coerce_str("1.5e18").unwrap(),
DynSolValue::Uint(U256::from_str("1500000000000000000").unwrap(), 256)
);

assert_eq!(
DynSolType::Uint(256).coerce_str("1e-3 ether").unwrap(),
DynSolValue::Uint(U256::from_str("1000000000000000").unwrap(), 256)
);
assert_eq!(
DynSolType::Uint(256).coerce_str("1.0e-3 ether").unwrap(),
DynSolValue::Uint(U256::from_str("1000000000000000").unwrap(), 256)
);
assert_eq!(
DynSolType::Uint(256).coerce_str("1.1e-3 ether").unwrap(),
DynSolValue::Uint(U256::from_str("1100000000000000").unwrap(), 256)
);

assert!(DynSolType::Uint(256).coerce_str("1e-18").is_err());
assert!(DynSolType::Uint(256).coerce_str("1 e18").is_err());
assert!(DynSolType::Uint(256).coerce_str("1ex").is_err());
assert!(DynSolType::Uint(256).coerce_str("1e").is_err());
}
}
Loading