Skip to content

Commit

Permalink
fix(core): properly parse genesis alloc storage (#2205)
Browse files Browse the repository at this point in the history
* fix(core): properly parse genesis alloc storage

 * Previously we were not properly parsing the storage field of
   GenesisAlloc. The first issue was in using serde(flatten), which is
   just incorrect because storage is not encoded flattened. The second
   issue was in the parsing of H256 keys and values. Some of the storage
   values in hive genesis examples were encoded in less than 64 hex
   characters, such as the string `0x12`. This would not succeed normal
   H256 parsing.
 * Introduce from_unformatted_hex_map to properly deserialize the
   storage map.
 * Modify existing genesis parsing tests to check parsed storage and
   code values against expected values.
 * Introduce new genesis parsing test from the hive smoke tests,
   checking full struct equality.

* remove unused from_unformatted hex

* make clippy happy
  • Loading branch information
Rjected committed Feb 27, 2023
1 parent 319b86a commit 537d0a9
Show file tree
Hide file tree
Showing 2 changed files with 258 additions and 9 deletions.
223 changes: 215 additions & 8 deletions ethers-core/src/utils/genesis.rs
Expand Up @@ -2,13 +2,13 @@ use std::collections::HashMap;

use crate::{
types::{Address, Bytes, H256, U256, U64},
utils::{from_int_or_hex, from_int_or_hex_opt, from_u64_or_hex_opt},
utils::{from_int_or_hex, from_int_or_hex_opt, from_u64_or_hex_opt, from_unformatted_hex_map},
};
use serde::{Deserialize, Serialize};

/// This represents the chain configuration, specifying the genesis block, header fields, and hard
/// fork switch blocks.
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct Genesis {
/// The fork configuration for this network.
Expand Down Expand Up @@ -133,7 +133,11 @@ pub struct GenesisAccount {
pub balance: U256,
#[serde(skip_serializing_if = "Option::is_none", default)]
pub code: Option<Bytes>,
#[serde(flatten, skip_serializing_if = "Option::is_none", default)]
#[serde(
skip_serializing_if = "Option::is_none",
deserialize_with = "from_unformatted_hex_map",
default
)]
pub storage: Option<HashMap<H256, H256>>,
}

Expand All @@ -142,7 +146,7 @@ pub struct GenesisAccount {
/// See [geth's `ChainConfig`
/// struct](https://github.com/ethereum/go-ethereum/blob/64dccf7aa411c5c7cd36090c3d9b9892945ae813/params/config.go#L349)
/// for the source of each field.
#[derive(Clone, Debug, Deserialize, Serialize, Default)]
#[derive(Clone, Debug, Deserialize, Serialize, Default, PartialEq, Eq)]
#[serde(default, rename_all = "camelCase")]
pub struct ChainConfig {
/// The network's chain ID.
Expand Down Expand Up @@ -248,11 +252,11 @@ const fn one() -> u64 {
}

/// Empty consensus configuration for proof-of-work networks.
#[derive(Clone, Debug, Deserialize, Serialize)]
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
pub struct EthashConfig {}

/// Consensus configuration for Clique.
#[derive(Clone, Debug, Deserialize, Serialize)]
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
pub struct CliqueConfig {
/// Number of seconds between blocks to enforce.
#[serde(default, skip_serializing_if = "Option::is_none")]
Expand All @@ -265,7 +269,12 @@ pub struct CliqueConfig {

#[cfg(test)]
mod tests {
use super::{Genesis, H256};
use super::{ChainConfig, Genesis, GenesisAccount, H256};
use crate::{
types::{Address, Bytes, H160, U256},
utils::EthashConfig,
};
use std::{collections::HashMap, str::FromStr};

#[test]
fn parse_hive_genesis() {
Expand Down Expand Up @@ -611,6 +620,204 @@ mod tests {
}
"#;

let _genesis: Genesis = serde_json::from_str(geth_genesis).unwrap();
let genesis: Genesis = serde_json::from_str(geth_genesis).unwrap();
let alloc_entry = genesis
.alloc
.get(&H160::from_str("0000000000000000000000000000000000000314").unwrap())
.expect("missing account for parsed genesis");
let storage = alloc_entry.storage.as_ref().expect("missing storage for parsed genesis");
let expected_storage = HashMap::from_iter(vec![
(
H256::from_str(
"0x0000000000000000000000000000000000000000000000000000000000000000",
)
.unwrap(),
H256::from_str(
"0x0000000000000000000000000000000000000000000000000000000000001234",
)
.unwrap(),
),
(
H256::from_str(
"0x6661e9d6d8b923d5bbaab1b96e1dd51ff6ea2a93520fdc9eb75d059238b8c5e9",
)
.unwrap(),
H256::from_str(
"0x0000000000000000000000000000000000000000000000000000000000000001",
)
.unwrap(),
),
]);
assert_eq!(storage, &expected_storage);

let expected_code = Bytes::from_str("0x60606040526000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff168063a223e05d1461006a578063abd1a0cf1461008d578063abfced1d146100d4578063e05c914a14610110578063e6768b451461014c575b610000565b346100005761007761019d565b6040518082815260200191505060405180910390f35b34610000576100be600480803573ffffffffffffffffffffffffffffffffffffffff169060200190919050506101a3565b6040518082815260200191505060405180910390f35b346100005761010e600480803573ffffffffffffffffffffffffffffffffffffffff169060200190919080359060200190919050506101ed565b005b346100005761014a600480803590602001909190803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050610236565b005b346100005761017960048080359060200190919080359060200190919080359060200190919050506103c4565b60405180848152602001838152602001828152602001935050505060405180910390f35b60005481565b6000600160008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205490505b919050565b80600160008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055505b5050565b7f6031a8d62d7c95988fa262657cd92107d90ed96e08d8f867d32f26edfe85502260405180905060405180910390a17f47e2689743f14e97f7dcfa5eec10ba1dff02f83b3d1d4b9c07b206cbbda66450826040518082815260200191505060405180910390a1817fa48a6b249a5084126c3da369fbc9b16827ead8cb5cdc094b717d3f1dcd995e2960405180905060405180910390a27f7890603b316f3509577afd111710f9ebeefa15e12f72347d9dffd0d65ae3bade81604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390a18073ffffffffffffffffffffffffffffffffffffffff167f7efef9ea3f60ddc038e50cccec621f86a0195894dc0520482abf8b5c6b659e4160405180905060405180910390a28181604051808381526020018273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019250505060405180910390a05b5050565b6000600060008585859250925092505b935093509390505600a165627a7a72305820aaf842d0d0c35c45622c5263cbb54813d2974d3999c8c38551d7c613ea2bc1170029").unwrap();
let code = alloc_entry.code.as_ref().expect("missing code for parsed genesis");
assert_eq!(code, &expected_code);
}

#[test]
fn test_hive_smoke_alloc_deserialize() {
let hive_genesis = r#"
{
"nonce": "0x0000000000000042",
"difficulty": "0x2123456",
"mixHash": "0x123456789abcdef123456789abcdef123456789abcdef123456789abcdef1234",
"coinbase": "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"timestamp": "0x123456",
"parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
"extraData": "0xfafbfcfd",
"gasLimit": "0x2fefd8",
"alloc": {
"dbdbdb2cbd23b783741e8d7fcf51e459b497e4a6": {
"balance": "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"
},
"e6716f9544a56c530d868e4bfbacb172315bdead": {
"balance": "0x11",
"code": "0x12"
},
"b9c015918bdaba24b4ff057a92a3873d6eb201be": {
"balance": "0x21",
"storage": {
"0x0000000000000000000000000000000000000000000000000000000000000001": "0x22"
}
},
"1a26338f0d905e295fccb71fa9ea849ffa12aaf4": {
"balance": "0x31",
"nonce": "0x32"
},
"0000000000000000000000000000000000000001": {
"balance": "0x41"
},
"0000000000000000000000000000000000000002": {
"balance": "0x51"
},
"0000000000000000000000000000000000000003": {
"balance": "0x61"
},
"0000000000000000000000000000000000000004": {
"balance": "0x71"
}
},
"config": {
"ethash": {},
"chainId": 10,
"homesteadBlock": 0,
"eip150Block": 0,
"eip155Block": 0,
"eip158Block": 0,
"byzantiumBlock": 0,
"constantinopleBlock": 0,
"petersburgBlock": 0,
"istanbulBlock": 0
}
}
"#;

let expected_genesis = Genesis {
nonce: 0x0000000000000042.into(),
difficulty: 0x2123456.into(),
mix_hash: H256::from_str("0x123456789abcdef123456789abcdef123456789abcdef123456789abcdef1234").unwrap(),
coinbase: Address::from_str("0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa").unwrap(),
timestamp: 0x123456.into(),
parent_hash: Some(H256::from_str("0x0000000000000000000000000000000000000000000000000000000000000000").unwrap()),
extra_data: Bytes::from_str("0xfafbfcfd").unwrap(),
gas_limit: 0x2fefd8.into(),
alloc: HashMap::from_iter(vec![
(
Address::from_str("0xdbdbdb2cbd23b783741e8d7fcf51e459b497e4a6").unwrap(),
GenesisAccount {
balance: U256::from_str("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff").unwrap(),
nonce: None,
code: None,
storage: None,
},
),
(
Address::from_str("0xe6716f9544a56c530d868e4bfbacb172315bdead").unwrap(),
GenesisAccount {
balance: U256::from_str("0x11").unwrap(),
nonce: None,
code: Some(Bytes::from_str("0x12").unwrap()),
storage: None,
},
),
(
Address::from_str("0xb9c015918bdaba24b4ff057a92a3873d6eb201be").unwrap(),
GenesisAccount {
balance: U256::from_str("0x21").unwrap(),
nonce: None,
code: None,
storage: Some(HashMap::from_iter(vec![
(
H256::from_str("0x0000000000000000000000000000000000000000000000000000000000000001").unwrap(),
H256::from_str("0x0000000000000000000000000000000000000000000000000000000000000022").unwrap(),
),
])),
},
),
(
Address::from_str("0x1a26338f0d905e295fccb71fa9ea849ffa12aaf4").unwrap(),
GenesisAccount {
balance: U256::from_str("0x31").unwrap(),
nonce: Some(0x32u64),
code: None,
storage: None,
},
),
(
Address::from_str("0x0000000000000000000000000000000000000001").unwrap(),
GenesisAccount {
balance: U256::from_str("0x41").unwrap(),
nonce: None,
code: None,
storage: None,
},
),
(
Address::from_str("0x0000000000000000000000000000000000000002").unwrap(),
GenesisAccount {
balance: U256::from_str("0x51").unwrap(),
nonce: None,
code: None,
storage: None,
},
),
(
Address::from_str("0x0000000000000000000000000000000000000003").unwrap(),
GenesisAccount {
balance: U256::from_str("0x61").unwrap(),
nonce: None,
code: None,
storage: None,
},
),
(
Address::from_str("0x0000000000000000000000000000000000000004").unwrap(),
GenesisAccount {
balance: U256::from_str("0x71").unwrap(),
nonce: None,
code: None,
storage: None,
},
),
]),
config: ChainConfig {
ethash: Some(EthashConfig{}),
chain_id: 10,
homestead_block: Some(0),
eip150_block: Some(0),
eip155_block: Some(0),
eip158_block: Some(0),
byzantium_block: Some(0),
constantinople_block: Some(0),
petersburg_block: Some(0),
istanbul_block: Some(0),
..Default::default()
},
..Default::default()
};

let deserialized_genesis: Genesis = serde_json::from_str(hive_genesis).unwrap();
assert_eq!(deserialized_genesis, expected_genesis, "deserialized genesis {deserialized_genesis:#?} does not match expected {expected_genesis:#?}");
}
}
44 changes: 43 additions & 1 deletion ethers-core/src/utils/mod.rs
Expand Up @@ -36,11 +36,12 @@ pub use rlp;
/// Re-export hex
pub use hex;

use crate::types::{Address, ParseI256Error, I256, U256, U64};
use crate::types::{Address, Bytes, ParseI256Error, H256, I256, U256, U64};
use elliptic_curve::sec1::ToEncodedPoint;
use ethabi::ethereum_types::FromDecStrErr;
use k256::{ecdsa::SigningKey, PublicKey as K256PublicKey};
use std::{
collections::HashMap,
convert::{TryFrom, TryInto},
fmt,
str::FromStr,
Expand Down Expand Up @@ -468,6 +469,47 @@ pub fn eip1559_default_estimator(base_fee_per_gas: U256, rewards: Vec<Vec<U256>>
(max_fee_per_gas, max_priority_fee_per_gas)
}

/// Converts a Bytes value into a H256, accepting inputs that are less than 32 bytes long. These
/// inputs will be left padded with zeros.
pub fn from_bytes_to_h256<'de, D>(bytes: Bytes) -> Result<H256, D::Error>
where
D: Deserializer<'de>,
{
if bytes.0.len() > 32 {
return Err(serde::de::Error::custom("input too long to be a H256"))
}

// left pad with zeros to 32 bytes
let mut padded = [0u8; 32];
padded[32 - bytes.0.len()..].copy_from_slice(&bytes.0);

// then convert to H256 without a panic
Ok(H256::from_slice(&padded))
}

/// Deserializes the input into an Option<HashMap<H256, H256>>, using from_unformatted_hex to
/// deserialize the keys and values.
pub fn from_unformatted_hex_map<'de, D>(
deserializer: D,
) -> Result<Option<HashMap<H256, H256>>, D::Error>
where
D: Deserializer<'de>,
{
let map = Option::<HashMap<Bytes, Bytes>>::deserialize(deserializer)?;
match map {
Some(mut map) => {
let mut res_map = HashMap::new();
for (k, v) in map.drain() {
let k_deserialized = from_bytes_to_h256::<'de, D>(k)?;
let v_deserialized = from_bytes_to_h256::<'de, D>(v)?;
res_map.insert(k_deserialized, v_deserialized);
}
Ok(Some(res_map))
}
None => Ok(None),
}
}

/// Deserializes the input into a U256, accepting both 0x-prefixed hex and decimal strings with
/// arbitrary precision, defined by serde_json's [`Number`](serde_json::Number).
pub fn from_int_or_hex<'de, D>(deserializer: D) -> Result<U256, D::Error>
Expand Down

0 comments on commit 537d0a9

Please sign in to comment.