From 537d0a9deb8b1fc549bf21b48684e9155f12707b Mon Sep 17 00:00:00 2001 From: Dan Cline <6798349+Rjected@users.noreply.github.com> Date: Mon, 27 Feb 2023 17:22:01 -0500 Subject: [PATCH] fix(core): properly parse genesis alloc storage (#2205) * 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 --- ethers-core/src/utils/genesis.rs | 223 +++++++++++++++++++++++++++++-- ethers-core/src/utils/mod.rs | 44 +++++- 2 files changed, 258 insertions(+), 9 deletions(-) diff --git a/ethers-core/src/utils/genesis.rs b/ethers-core/src/utils/genesis.rs index 87299d1f..58725f90 100644 --- a/ethers-core/src/utils/genesis.rs +++ b/ethers-core/src/utils/genesis.rs @@ -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. @@ -133,7 +133,11 @@ pub struct GenesisAccount { pub balance: U256, #[serde(skip_serializing_if = "Option::is_none", default)] pub code: Option, - #[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>, } @@ -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. @@ -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")] @@ -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() { @@ -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:#?}"); } } diff --git a/ethers-core/src/utils/mod.rs b/ethers-core/src/utils/mod.rs index 542e543f..7f1e724d 100644 --- a/ethers-core/src/utils/mod.rs +++ b/ethers-core/src/utils/mod.rs @@ -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, @@ -468,6 +469,47 @@ pub fn eip1559_default_estimator(base_fee_per_gas: U256, rewards: Vec> (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 +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>, using from_unformatted_hex to +/// deserialize the keys and values. +pub fn from_unformatted_hex_map<'de, D>( + deserializer: D, +) -> Result>, D::Error> +where + D: Deserializer<'de>, +{ + let map = Option::>::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