fix(core): format_units overflow (#1894)
* fix(core): format_units overflow * fix: formatting * remove dbg * add comment and rerun ci * tests * fix test * refactor: move magic nums to consts Co-authored-by: Georgios Konstantopoulos <me@gakonst.com>
This commit is contained in:
parent
b0ef1343dd
commit
97eecaf03b
|
@ -41,6 +41,11 @@ use std::{
|
||||||
};
|
};
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
|
/// I256 overflows for numbers wider than 77 units.
|
||||||
|
const OVERFLOW_I256_UNITS: usize = 77;
|
||||||
|
/// U256 overflows for numbers wider than 78 units.
|
||||||
|
const OVERFLOW_U256_UNITS: usize = 78;
|
||||||
|
|
||||||
/// Re-export of serde-json
|
/// Re-export of serde-json
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
pub mod __serde_json {
|
pub mod __serde_json {
|
||||||
|
@ -158,29 +163,35 @@ where
|
||||||
T: Into<ParseUnits>,
|
T: Into<ParseUnits>,
|
||||||
K: TryInto<Units, Error = ConversionError>,
|
K: TryInto<Units, Error = ConversionError>,
|
||||||
{
|
{
|
||||||
let units = units.try_into()?;
|
let units: usize = units.try_into()?.into();
|
||||||
match amount.into() {
|
let amount = amount.into();
|
||||||
|
|
||||||
|
match amount {
|
||||||
|
// 2**256 ~= 1.16e77
|
||||||
|
ParseUnits::U256(_) if units >= OVERFLOW_U256_UNITS => {
|
||||||
|
return Err(ConversionError::ParseOverflow)
|
||||||
|
}
|
||||||
|
// 2**255 ~= 5.79e76
|
||||||
|
ParseUnits::I256(_) if units >= OVERFLOW_I256_UNITS => {
|
||||||
|
return Err(ConversionError::ParseOverflow)
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
};
|
||||||
|
let exp10 = U256::exp10(units);
|
||||||
|
|
||||||
|
// `decimals` are formatted twice because U256 does not support alignment (`:0>width`).
|
||||||
|
match amount {
|
||||||
ParseUnits::U256(amount) => {
|
ParseUnits::U256(amount) => {
|
||||||
let amount_decimals = amount % U256::from(10_u128.pow(units.as_num()));
|
let integer = amount / exp10;
|
||||||
let amount_integer = amount / U256::from(10_u128.pow(units.as_num()));
|
let decimals = (amount % exp10).to_string();
|
||||||
Ok(format!(
|
Ok(format!("{integer}.{decimals:0>units$}"))
|
||||||
"{}.{:0width$}",
|
|
||||||
amount_integer,
|
|
||||||
amount_decimals.as_u128(),
|
|
||||||
width = units.as_num() as usize
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
ParseUnits::I256(amount) => {
|
ParseUnits::I256(amount) => {
|
||||||
|
let exp10 = I256::from_raw(exp10);
|
||||||
let sign = if amount.is_negative() { "-" } else { "" };
|
let sign = if amount.is_negative() { "-" } else { "" };
|
||||||
let amount_decimals = amount % I256::from(10_u128.pow(units.as_num()));
|
let integer = (amount / exp10).twos_complement();
|
||||||
let amount_integer = amount / I256::from(10_u128.pow(units.as_num()));
|
let decimals = ((amount % exp10).twos_complement()).to_string();
|
||||||
Ok(format!(
|
Ok(format!("{sign}{integer}.{decimals:0>units$}"))
|
||||||
"{}{}.{:0width$}",
|
|
||||||
sign,
|
|
||||||
amount_integer.twos_complement(),
|
|
||||||
amount_decimals.twos_complement().as_u128(),
|
|
||||||
width = units.as_num() as usize
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -546,7 +557,7 @@ mod tests {
|
||||||
format_units(U256::from_dec_str("1005633240123456789").unwrap(), "ether").unwrap();
|
format_units(U256::from_dec_str("1005633240123456789").unwrap(), "ether").unwrap();
|
||||||
assert_eq!(eth, "1.005633240123456789");
|
assert_eq!(eth, "1.005633240123456789");
|
||||||
|
|
||||||
let eth = format_units(255u8, 4).unwrap();
|
let eth = format_units(u8::MAX, 4).unwrap();
|
||||||
assert_eq!(eth, "0.0255");
|
assert_eq!(eth, "0.0255");
|
||||||
|
|
||||||
let eth = format_units(u16::MAX, "ether").unwrap();
|
let eth = format_units(u16::MAX, "ether").unwrap();
|
||||||
|
@ -562,6 +573,15 @@ mod tests {
|
||||||
|
|
||||||
let eth = format_units(u128::MAX, 36).unwrap();
|
let eth = format_units(u128::MAX, 36).unwrap();
|
||||||
assert_eq!(eth, "340.282366920938463463374607431768211455");
|
assert_eq!(eth, "340.282366920938463463374607431768211455");
|
||||||
|
|
||||||
|
let eth = format_units(U256::MAX, 77).unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
eth,
|
||||||
|
"1.15792089237316195423570985008687907853269984665640564039457584007913129639935"
|
||||||
|
);
|
||||||
|
|
||||||
|
let err = format_units(U256::MAX, 78).unwrap_err();
|
||||||
|
assert!(matches!(err, ConversionError::ParseOverflow));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -599,6 +619,15 @@ mod tests {
|
||||||
|
|
||||||
let eth = format_units(i128::MIN, 36).unwrap();
|
let eth = format_units(i128::MIN, 36).unwrap();
|
||||||
assert_eq!(eth, "-170.141183460469231731687303715884105728");
|
assert_eq!(eth, "-170.141183460469231731687303715884105728");
|
||||||
|
|
||||||
|
let eth = format_units(I256::MIN, 76).unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
eth,
|
||||||
|
"-5.7896044618658097711785492504343953926634992332820282019728792003956564819968"
|
||||||
|
);
|
||||||
|
|
||||||
|
let err = format_units(I256::MIN, 77).unwrap_err();
|
||||||
|
assert!(matches!(err, ConversionError::ParseOverflow));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
Loading…
Reference in New Issue