Skip to content
This repository has been archived by the owner on Oct 19, 2024. It is now read-only.

fix(core): fix decimal string IntOrHex parsing #2359

Merged
merged 1 commit into from
Apr 18, 2023
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
62 changes: 62 additions & 0 deletions ethers-core/src/utils/genesis.rs
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,7 @@ pub struct GenesisAccount {
default
)]
pub nonce: Option<u64>,
#[serde(deserialize_with = "from_int_or_hex")]
pub balance: U256,
#[serde(skip_serializing_if = "Option::is_none", default)]
pub code: Option<Bytes>,
Expand Down Expand Up @@ -310,6 +311,67 @@ mod tests {
let _genesis: Genesis = serde_json::from_str(geth_genesis).unwrap();
}

#[test]
fn parse_non_hex_prefixed_balance() {
// tests that we can parse balance / difficulty fields that are either hex or decimal
let example_balance_json = r#"
{
"nonce": "0x0000000000000042",
"difficulty": "34747478",
"mixHash": "0x123456789abcdef123456789abcdef123456789abcdef123456789abcdef1234",
"coinbase": "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"timestamp": "0x123456",
"parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
"extraData": "0xfafbfcfd",
"gasLimit": "0x2fefd8",
"alloc": {
"0x3E951C9f69a06Bc3AD71fF7358DbC56bEd94b9F2": {
"balance": "1000000000000000000000000000"
},
"0xe228C30d4e5245f967ac21726d5412dA27aD071C": {
"balance": "1000000000000000000000000000"
},
"0xD59Ce7Ccc6454a2D2C2e06bbcf71D0Beb33480eD": {
"balance": "1000000000000000000000000000"
},
"0x1CF4D54414eF51b41f9B2238c57102ab2e61D1F2": {
"balance": "1000000000000000000000000000"
},
"0x249bE3fDEd872338C733cF3975af9736bdCb9D4D": {
"balance": "1000000000000000000000000000"
},
"0x3fCd1bff94513712f8cD63d1eD66776A67D5F78e": {
"balance": "1000000000000000000000000000"
}
},
"config": {
"ethash": {},
"chainId": 10,
"homesteadBlock": 0,
"eip150Block": 0,
"eip155Block": 0,
"eip158Block": 0,
"byzantiumBlock": 0,
"constantinopleBlock": 0,
"petersburgBlock": 0,
"istanbulBlock": 0
}
}
"#;

let genesis: Genesis = serde_json::from_str(example_balance_json).unwrap();

// check difficulty against hex ground truth
let expected_difficulty = U256::from_str("0x2123456").unwrap();
assert_eq!(expected_difficulty, genesis.difficulty);

// check all alloc balances
let dec_balance = U256::from_dec_str("1000000000000000000000000000").unwrap();
for alloc in &genesis.alloc {
assert_eq!(alloc.1.balance, dec_balance);
}
}

#[test]
fn parse_hive_rpc_genesis() {
let geth_genesis = r#"
Expand Down
53 changes: 47 additions & 6 deletions ethers-core/src/utils/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -515,14 +515,20 @@ where
{
#[derive(Deserialize)]
#[serde(untagged)]
enum IntOrHex {
enum IntOrString {
Int(serde_json::Number),
Hex(String),
JsonString(String),
}

match IntOrHex::deserialize(deserializer)? {
IntOrHex::Hex(s) => U256::from_str(s.as_str()).map_err(serde::de::Error::custom),
IntOrHex::Int(n) => U256::from_dec_str(&n.to_string()).map_err(serde::de::Error::custom),
match IntOrString::deserialize(deserializer)? {
IntOrString::JsonString(s) => {
if s.starts_with("0x") {
U256::from_str(s.as_str()).map_err(serde::de::Error::custom)
} else {
U256::from_dec_str(&s).map_err(serde::de::Error::custom)
}
Comment on lines -523 to +529
Copy link
Owner

Choose a reason for hiding this comment

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

cc @prestwich for ethers-next, you wouldn't believe how much time pinpointing this took us

}
IntOrString::Int(n) => U256::from_dec_str(&n.to_string()).map_err(serde::de::Error::custom),
}
}

Expand All @@ -540,7 +546,13 @@ where
}

match IntOrHex::deserialize(deserializer)? {
IntOrHex::Hex(s) => U64::from_str(s.as_str()).map_err(serde::de::Error::custom),
IntOrHex::Hex(s) => {
if s.starts_with("0x") {
U64::from_str(s.as_str()).map_err(serde::de::Error::custom)
} else {
U64::from_dec_str(&s).map_err(serde::de::Error::custom)
}
}
IntOrHex::Int(n) => U64::from_dec_str(&n.to_string()).map_err(serde::de::Error::custom),
}
}
Expand Down Expand Up @@ -1105,4 +1117,33 @@ mod tests {
let rewards_overflow: Vec<Vec<U256>> = vec![vec![overflow], vec![overflow]];
assert_eq!(estimate_priority_fee(rewards_overflow), overflow);
}

#[test]
fn int_or_hex_combinations() {
// make sure we can deserialize all combinations of int and hex
// including large numbers that would overflow u64
//
// format: (string, expected value)
let cases = vec![
// hex strings
("\"0x0\"", U256::from(0)),
("\"0x1\"", U256::from(1)),
("\"0x10\"", U256::from(16)),
("\"0x100000000000000000000000000000000000000000000000000\"", U256::from_dec_str("1606938044258990275541962092341162602522202993782792835301376").unwrap()),
// small num, both num and str form
("10", U256::from(10)),
("\"10\"", U256::from(10)),
// max u256, in both num and str form
("115792089237316195423570985008687907853269984665640564039457584007913129639935", U256::from_dec_str("115792089237316195423570985008687907853269984665640564039457584007913129639935").unwrap()),
("\"115792089237316195423570985008687907853269984665640564039457584007913129639935\"", U256::from_dec_str("115792089237316195423570985008687907853269984665640564039457584007913129639935").unwrap())
];

#[derive(Deserialize)]
struct TestUint(#[serde(deserialize_with = "from_int_or_hex")] U256);

for (string, expected) in cases {
let test: TestUint = serde_json::from_str(string).unwrap();
assert_eq!(test.0, expected);
}
}
}