use std::{
collections::BTreeMap,
fs::{self, File},
io::{self, Error, ErrorKind, Write},
path::Path,
};
use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize, Eq, PartialEq, Clone)]
#[serde(rename_all = "camelCase")]
pub struct Genesis {
#[serde(skip_serializing_if = "Option::is_none")]
pub config: Option<ChainConfig>,
#[serde(with = "crate::codec::serde::hex_0x_primitive_types_u256")]
pub nonce: primitive_types::U256,
#[serde(with = "crate::codec::serde::hex_0x_primitive_types_u256")]
pub timestamp: primitive_types::U256,
#[serde(skip_serializing_if = "Option::is_none")]
pub extra_data: Option<String>,
#[serde(with = "crate::codec::serde::hex_0x_primitive_types_u256")]
pub gas_limit: primitive_types::U256,
#[serde(with = "crate::codec::serde::hex_0x_primitive_types_u256")]
pub difficulty: primitive_types::U256,
#[serde(skip_serializing_if = "Option::is_none")]
pub mix_hash: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub coinbase: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub alloc: Option<BTreeMap<String, AllocAccount>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub airdrop_hash: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub airdrop_amount: Option<String>,
#[serde(with = "crate::codec::serde::hex_0x_primitive_types_u256")]
pub number: primitive_types::U256,
#[serde(with = "crate::codec::serde::hex_0x_primitive_types_u256")]
pub gas_used: primitive_types::U256,
#[serde(skip_serializing_if = "Option::is_none")]
pub parent_hash: Option<String>,
#[serde(rename = "baseFeePerGas", skip_serializing_if = "Option::is_none")]
pub base_fee: Option<String>,
}
pub const DEFAULT_INITIAL_AMOUNT: &str = "0x204FCE5E3E25026110000000";
impl Default for Genesis {
fn default() -> Self {
Self::default()
}
}
impl Genesis {
pub fn default() -> Self {
let mut alloc = BTreeMap::new();
alloc.insert(
String::from("6f0f6DA1852857d7789f68a28bba866671f3880D"),
AllocAccount::default(),
);
Self {
config: Some(ChainConfig::default()),
nonce: primitive_types::U256::default(),
timestamp: primitive_types::U256::default(),
extra_data: Some(String::from("0x00")),
gas_limit: primitive_types::U256::from_str_radix("0x1312D00", 16).unwrap(),
difficulty: primitive_types::U256::default(),
mix_hash: Some(String::from(
"0x0000000000000000000000000000000000000000000000000000000000000000",
)),
coinbase: Some(String::from("0x0000000000000000000000000000000000000000")),
alloc: Some(alloc),
airdrop_hash: None,
airdrop_amount: None,
number: primitive_types::U256::default(),
gas_used: primitive_types::U256::default(),
parent_hash: Some(String::from(
"0x0000000000000000000000000000000000000000000000000000000000000000",
)),
base_fee: None,
}
}
pub fn new(seed_eth_addrs: Vec<String>) -> io::Result<Self> {
let max_total_alloc = i128::MAX;
let total_keys = seed_eth_addrs.len();
let alloc_per_key = if total_keys > 0 {
max_total_alloc / total_keys as i128
} else {
0i128
};
let alloc_per_key = alloc_per_key / 2;
let mut default_alloc = AllocAccount::default();
default_alloc.balance = primitive_types::U256::from(alloc_per_key);
let mut allocs = BTreeMap::new();
for eth_addr in seed_eth_addrs.iter() {
allocs.insert(
eth_addr.trim_start_matches("0x").to_string(),
default_alloc.clone(),
);
}
let mut subnet_evm_genesis = Self::default();
subnet_evm_genesis.alloc = Some(allocs);
Ok(subnet_evm_genesis)
}
pub fn encode_json(&self) -> io::Result<String> {
serde_json::to_string(&self)
.map_err(|e| Error::new(ErrorKind::Other, format!("failed to serialize JSON {}", e)))
}
pub fn to_bytes(&self) -> io::Result<Vec<u8>> {
serde_json::to_vec(self)
.map_err(|e| Error::new(ErrorKind::Other, format!("failed encode JSON {}", e)))
}
pub fn sync(&self, file_path: &str) -> io::Result<()> {
log::info!("syncing Genesis to '{}'", file_path);
let path = Path::new(file_path);
if let Some(parent_dir) = path.parent() {
log::info!("creating parent dir '{}'", parent_dir.display());
fs::create_dir_all(parent_dir)?;
}
let d = serde_json::to_vec(self)
.map_err(|e| Error::new(ErrorKind::Other, format!("failed to serialize JSON {}", e)))?;
let mut f = File::create(file_path)?;
f.write_all(&d)?;
Ok(())
}
}
#[derive(Debug, Serialize, Deserialize, Eq, PartialEq, Clone)]
#[serde(rename_all = "camelCase")]
pub struct ChainConfig {
#[serde(skip_serializing_if = "Option::is_none")]
pub chain_id: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub fee_config: Option<FeeConfig>,
#[serde(skip_serializing_if = "Option::is_none")]
pub allow_fee_recipients: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub homestead_block: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub eip150_block: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub eip150_hash: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub eip155_block: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub eip158_block: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub byzantium_block: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub constantinople_block: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub petersburg_block: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub istanbul_block: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub muir_glacier_block: Option<u64>,
#[serde(rename = "subnetEVMTimestamp", skip_serializing_if = "Option::is_none")]
pub subnet_evm_timestamp: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub contract_deployer_allow_list_config: Option<ContractDeployerAllowListConfig>,
#[serde(skip_serializing_if = "Option::is_none")]
pub contract_native_minter_config: Option<ContractNativeMinterConfig>,
#[serde(skip_serializing_if = "Option::is_none")]
pub tx_allow_list_config: Option<TxAllowListConfig>,
#[serde(skip_serializing_if = "Option::is_none")]
pub fee_manager_config: Option<FeeManagerConfig>,
}
impl Default for ChainConfig {
fn default() -> Self {
Self::default()
}
}
impl ChainConfig {
pub fn default() -> Self {
Self {
chain_id: Some(2000777),
fee_config: Some(FeeConfig::default()),
allow_fee_recipients: None,
homestead_block: Some(0),
eip150_block: Some(0),
eip150_hash: Some(String::from(
"0x2086799aeebeae135c246c65021c82b4e15a2c451340993aacfd2751886514f0",
)),
eip155_block: Some(0),
eip158_block: Some(0),
byzantium_block: Some(0),
constantinople_block: Some(0),
petersburg_block: Some(0),
istanbul_block: Some(0),
muir_glacier_block: Some(0),
subnet_evm_timestamp: Some(0),
contract_deployer_allow_list_config: None,
contract_native_minter_config: None,
tx_allow_list_config: None,
fee_manager_config: None,
}
}
}
#[derive(Debug, Serialize, Deserialize, Eq, PartialEq, Clone)]
#[serde(rename_all = "camelCase")]
pub struct FeeConfig {
#[serde(skip_serializing_if = "Option::is_none")]
pub gas_limit: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub target_block_rate: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub min_base_fee: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub target_gas: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub base_fee_change_denominator: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub min_block_gas_cost: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub max_block_gas_cost: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub block_gas_cost_step: Option<u64>,
}
impl Default for FeeConfig {
fn default() -> Self {
Self::default()
}
}
impl FeeConfig {
pub fn default() -> Self {
Self {
gas_limit: Some(20_000_000),
target_block_rate: Some(2),
min_base_fee: Some(1_000_000_000),
target_gas: Some(100_000_000),
base_fee_change_denominator: Some(48),
min_block_gas_cost: Some(0),
max_block_gas_cost: Some(10_000_000),
block_gas_cost_step: Some(500_000),
}
}
}
#[derive(Debug, Serialize, Deserialize, Eq, PartialEq, Clone)]
#[serde(rename_all = "camelCase")]
pub struct ContractDeployerAllowListConfig {
#[serde(rename = "adminAddresses", skip_serializing_if = "Option::is_none")]
pub allow_list_admins: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub block_timestamp: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub disable: Option<bool>,
}
impl Default for ContractDeployerAllowListConfig {
fn default() -> Self {
Self::default()
}
}
impl ContractDeployerAllowListConfig {
pub fn default() -> Self {
Self {
allow_list_admins: None,
block_timestamp: Some(0),
disable: None,
}
}
}
#[derive(Debug, Serialize, Deserialize, Eq, PartialEq, Clone)]
#[serde(rename_all = "camelCase")]
pub struct ContractNativeMinterConfig {
#[serde(rename = "adminAddresses", skip_serializing_if = "Option::is_none")]
pub allow_list_admins: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub block_timestamp: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub disable: Option<bool>,
}
impl Default for ContractNativeMinterConfig {
fn default() -> Self {
Self::default()
}
}
impl ContractNativeMinterConfig {
pub fn default() -> Self {
Self {
allow_list_admins: None,
block_timestamp: Some(0),
disable: None,
}
}
}
#[derive(Debug, Serialize, Deserialize, Eq, PartialEq, Clone)]
#[serde(rename_all = "camelCase")]
pub struct TxAllowListConfig {
#[serde(rename = "adminAddresses", skip_serializing_if = "Option::is_none")]
pub allow_list_admins: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub block_timestamp: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub disable: Option<bool>,
}
impl Default for TxAllowListConfig {
fn default() -> Self {
Self::default()
}
}
impl TxAllowListConfig {
pub fn default() -> Self {
Self {
allow_list_admins: None,
block_timestamp: Some(0),
disable: None,
}
}
}
#[derive(Debug, Serialize, Deserialize, Eq, PartialEq, Clone)]
#[serde(rename_all = "camelCase")]
pub struct FeeManagerConfig {
#[serde(rename = "adminAddresses", skip_serializing_if = "Option::is_none")]
pub allow_list_admins: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub block_timestamp: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub disable: Option<bool>,
}
impl Default for FeeManagerConfig {
fn default() -> Self {
Self::default()
}
}
impl FeeManagerConfig {
pub fn default() -> Self {
Self {
allow_list_admins: None,
block_timestamp: Some(0),
disable: None,
}
}
}
#[derive(Debug, Serialize, Deserialize, Eq, PartialEq, Clone)]
#[serde(rename_all = "camelCase")]
pub struct AllocAccount {
#[serde(skip_serializing_if = "Option::is_none")]
pub code: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub storage: Option<BTreeMap<String, String>>,
#[serde(with = "crate::codec::serde::hex_0x_primitive_types_u256")]
pub balance: primitive_types::U256,
#[serde(skip_serializing_if = "Option::is_none")]
pub mcbalance: Option<BTreeMap<String, u64>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub nonce: Option<u64>,
}
impl Default for AllocAccount {
fn default() -> Self {
Self::default()
}
}
impl AllocAccount {
pub fn default() -> Self {
Self {
code: None,
storage: None,
balance: primitive_types::U256::from_str_radix(DEFAULT_INITIAL_AMOUNT, 16).unwrap(),
mcbalance: None,
nonce: None,
}
}
}
#[test]
fn test_parse() {
let _ = env_logger::builder().is_test(true).try_init();
let resp: Genesis = serde_json::from_str(
r#"
{
"unknown1": "field1",
"unknown2": "field2",
"config": {
"chainId": 2000777,
"homesteadBlock": 0,
"eip150Block": 0,
"eip150Hash": "0x2086799aeebeae135c246c65021c82b4e15a2c451340993aacfd2751886514f0",
"eip155Block": 0,
"eip158Block": 0,
"byzantiumBlock": 0,
"constantinopleBlock": 0,
"petersburgBlock": 0,
"istanbulBlock": 0,
"muirGlacierBlock": 0,
"subnetEVMTimestamp": 0,
"feeConfig": {
"gasLimit": 20000000,
"minBaseFee": 1000000000,
"targetGas": 100000000,
"baseFeeChangeDenominator": 48,
"minBlockGasCost": 0,
"maxBlockGasCost": 10000000,
"targetBlockRate": 2,
"blockGasCostStep": 500000
}
},
"alloc": {
"6f0f6DA1852857d7789f68a28bba866671f3880D": {
"balance": "0x204FCE5E3E25026110000000"
}
},
"nonce": "0x0",
"timestamp": "0x0",
"extraData": "0x00",
"gasLimit": "0x1312D00",
"difficulty": "0x0",
"mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
"coinbase": "0x0000000000000000000000000000000000000000",
"number": "0x0",
"gasUsed": "0x0",
"parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000"
}
"#,
)
.unwrap();
let expected = Genesis::default();
assert_eq!(resp, expected);
let d = Genesis::default();
let d = d.encode_json().unwrap();
log::info!("{}", d);
}