1use std::{
2 collections::BTreeMap,
3 fs::{self, File},
4 io::{self, Error, ErrorKind, Write},
5 path::Path,
6};
7
8use serde::{Deserialize, Serialize};
9
10#[derive(Debug, Serialize, Deserialize, Eq, PartialEq, Clone)]
13#[serde(rename_all = "camelCase")]
14pub struct Genesis {
15 #[serde(skip_serializing_if = "Option::is_none")]
16 pub config: Option<ChainConfig>,
17
18 #[serde(with = "crate::codec::serde::hex_0x_primitive_types_u256")]
19 pub nonce: primitive_types::U256,
20 #[serde(with = "crate::codec::serde::hex_0x_primitive_types_u256")]
21 pub timestamp: primitive_types::U256,
22
23 #[serde(skip_serializing_if = "Option::is_none")]
24 pub extra_data: Option<String>,
25
26 #[serde(with = "crate::codec::serde::hex_0x_primitive_types_u256")]
31 pub gas_limit: primitive_types::U256,
32 #[serde(with = "crate::codec::serde::hex_0x_primitive_types_u256")]
33 pub difficulty: primitive_types::U256,
34
35 #[serde(skip_serializing_if = "Option::is_none")]
36 pub mix_hash: Option<String>,
37 #[serde(skip_serializing_if = "Option::is_none")]
38 pub coinbase: Option<String>,
39
40 #[serde(skip_serializing_if = "Option::is_none")]
45 pub alloc: Option<BTreeMap<String, AllocAccount>>,
46
47 #[serde(skip_serializing_if = "Option::is_none")]
49 pub airdrop_hash: Option<String>,
50 #[serde(skip_serializing_if = "Option::is_none")]
51 pub airdrop_amount: Option<String>,
52
53 #[serde(with = "crate::codec::serde::hex_0x_primitive_types_u256")]
54 pub number: primitive_types::U256,
55 #[serde(with = "crate::codec::serde::hex_0x_primitive_types_u256")]
56 pub gas_used: primitive_types::U256,
57 #[serde(skip_serializing_if = "Option::is_none")]
58 pub parent_hash: Option<String>,
59 #[serde(rename = "baseFeePerGas", skip_serializing_if = "Option::is_none")]
60 pub base_fee: Option<String>,
61}
62
63pub const DEFAULT_INITIAL_AMOUNT: &str = "0x204FCE5E3E25026110000000";
70
71impl Default for Genesis {
72 fn default() -> Self {
73 let mut alloc = BTreeMap::new();
74 alloc.insert(
75 String::from("6f0f6DA1852857d7789f68a28bba866671f3880D"),
77 AllocAccount::default(),
78 );
79 Self {
80 config: Some(ChainConfig::default()),
81
82 nonce: primitive_types::U256::default(),
83 timestamp: primitive_types::U256::default(),
84 extra_data: Some(String::from("0x00")),
85
86 gas_limit: primitive_types::U256::from_str_radix("0x1312D00", 16).unwrap(),
89
90 difficulty: primitive_types::U256::default(),
91 mix_hash: Some(String::from(
92 "0x0000000000000000000000000000000000000000000000000000000000000000",
93 )),
94 coinbase: Some(String::from("0x0000000000000000000000000000000000000000")),
95
96 alloc: Some(alloc),
97
98 airdrop_hash: None,
99 airdrop_amount: None,
100
101 number: primitive_types::U256::default(),
102 gas_used: primitive_types::U256::default(),
103 parent_hash: Some(String::from(
104 "0x0000000000000000000000000000000000000000000000000000000000000000",
105 )),
106 base_fee: None,
107 }
108 }
109}
110
111impl Genesis {
112 pub fn new(seed_eth_addrs: Vec<String>) -> io::Result<Self> {
114 let max_total_alloc = i128::MAX;
116 let total_keys = seed_eth_addrs.len();
117 let alloc_per_key = if total_keys > 0 {
118 max_total_alloc / total_keys as i128
119 } else {
120 0i128
121 };
122 let alloc_per_key = alloc_per_key / 2;
124
125 let default_alloc = AllocAccount {
126 balance: primitive_types::U256::from(alloc_per_key),
127 ..Default::default()
128 };
129
130 let mut allocs = BTreeMap::new();
131 for eth_addr in seed_eth_addrs.iter() {
132 allocs.insert(
133 eth_addr.trim_start_matches("0x").to_string(),
134 default_alloc.clone(),
135 );
136 }
137
138 let subnet_evm_genesis = Self {
139 alloc: Some(allocs),
140 ..Self::default()
141 };
142
143 Ok(subnet_evm_genesis)
144 }
145
146 pub fn encode_json(&self) -> io::Result<String> {
147 serde_json::to_string(&self)
148 .map_err(|e| Error::new(ErrorKind::Other, format!("failed to serialize JSON {}", e)))
149 }
150
151 pub fn to_bytes(&self) -> io::Result<Vec<u8>> {
153 serde_json::to_vec(self)
154 .map_err(|e| Error::new(ErrorKind::Other, format!("failed encode JSON {}", e)))
155 }
156
157 pub fn sync(&self, file_path: &str) -> io::Result<()> {
160 log::info!("syncing Genesis to '{}'", file_path);
161 let path = Path::new(file_path);
162 if let Some(parent_dir) = path.parent() {
163 log::info!("creating parent dir '{}'", parent_dir.display());
164 fs::create_dir_all(parent_dir)?;
165 }
166
167 let d = serde_json::to_vec(self)
168 .map_err(|e| Error::new(ErrorKind::Other, format!("failed to serialize JSON {}", e)))?;
169
170 let mut f = File::create(file_path)?;
171 f.write_all(&d)?;
172
173 Ok(())
174 }
175}
176
177#[derive(Debug, Serialize, Deserialize, Eq, PartialEq, Clone)]
180#[serde(rename_all = "camelCase")]
181pub struct ChainConfig {
182 #[serde(skip_serializing_if = "Option::is_none")]
183 pub chain_id: Option<u64>,
184
185 #[serde(skip_serializing_if = "Option::is_none")]
186 pub fee_config: Option<FeeConfig>,
187 #[serde(skip_serializing_if = "Option::is_none")]
188 pub allow_fee_recipients: Option<bool>,
189
190 #[serde(skip_serializing_if = "Option::is_none")]
191 pub homestead_block: Option<u64>,
192
193 #[serde(skip_serializing_if = "Option::is_none")]
194 pub eip150_block: Option<u64>,
195 #[serde(skip_serializing_if = "Option::is_none")]
196 pub eip150_hash: Option<String>,
197
198 #[serde(skip_serializing_if = "Option::is_none")]
199 pub eip155_block: Option<u64>,
200 #[serde(skip_serializing_if = "Option::is_none")]
201 pub eip158_block: Option<u64>,
202
203 #[serde(skip_serializing_if = "Option::is_none")]
204 pub byzantium_block: Option<u64>,
205 #[serde(skip_serializing_if = "Option::is_none")]
206 pub constantinople_block: Option<u64>,
207 #[serde(skip_serializing_if = "Option::is_none")]
208 pub petersburg_block: Option<u64>,
209 #[serde(skip_serializing_if = "Option::is_none")]
210 pub istanbul_block: Option<u64>,
211 #[serde(skip_serializing_if = "Option::is_none")]
212 pub muir_glacier_block: Option<u64>,
213
214 #[serde(rename = "subnetEVMTimestamp", skip_serializing_if = "Option::is_none")]
215 pub subnet_evm_timestamp: Option<u64>,
216
217 #[serde(skip_serializing_if = "Option::is_none")]
219 pub contract_deployer_allow_list_config: Option<ContractDeployerAllowListConfig>,
220 #[serde(skip_serializing_if = "Option::is_none")]
222 pub contract_native_minter_config: Option<ContractNativeMinterConfig>,
223 #[serde(skip_serializing_if = "Option::is_none")]
225 pub tx_allow_list_config: Option<TxAllowListConfig>,
226 #[serde(skip_serializing_if = "Option::is_none")]
228 pub fee_manager_config: Option<FeeManagerConfig>,
229}
230
231impl Default for ChainConfig {
232 fn default() -> Self {
234 Self {
235 chain_id: Some(2000777),
239
240 fee_config: Some(FeeConfig::default()),
241 allow_fee_recipients: None,
242
243 homestead_block: Some(0),
244
245 eip150_block: Some(0),
246 eip150_hash: Some(String::from(
247 "0x2086799aeebeae135c246c65021c82b4e15a2c451340993aacfd2751886514f0",
248 )),
249
250 eip155_block: Some(0),
251 eip158_block: Some(0),
252
253 byzantium_block: Some(0),
254 constantinople_block: Some(0),
255 petersburg_block: Some(0),
256 istanbul_block: Some(0),
257 muir_glacier_block: Some(0),
258
259 subnet_evm_timestamp: Some(0),
260
261 contract_deployer_allow_list_config: None,
262 contract_native_minter_config: None,
263 tx_allow_list_config: None,
264 fee_manager_config: None,
265 }
266 }
267}
268
269#[derive(Debug, Serialize, Deserialize, Eq, PartialEq, Clone)]
272#[serde(rename_all = "camelCase")]
273pub struct FeeConfig {
274 #[serde(skip_serializing_if = "Option::is_none")]
277 pub gas_limit: Option<u64>,
278 #[serde(skip_serializing_if = "Option::is_none")]
279 pub target_block_rate: Option<u64>,
280
281 #[serde(skip_serializing_if = "Option::is_none")]
282 pub min_base_fee: Option<u64>,
283 #[serde(skip_serializing_if = "Option::is_none")]
284 pub target_gas: Option<u64>,
285 #[serde(skip_serializing_if = "Option::is_none")]
286 pub base_fee_change_denominator: Option<u64>,
287
288 #[serde(skip_serializing_if = "Option::is_none")]
292 pub min_block_gas_cost: Option<u64>,
293 #[serde(skip_serializing_if = "Option::is_none")]
297 pub max_block_gas_cost: Option<u64>,
298
299 #[serde(skip_serializing_if = "Option::is_none")]
300 pub block_gas_cost_step: Option<u64>,
301}
302
303impl Default for FeeConfig {
304 fn default() -> Self {
310 Self {
311 gas_limit: Some(20_000_000),
313 target_block_rate: Some(2),
314
315 min_base_fee: Some(1_000_000_000),
316 target_gas: Some(100_000_000),
317 base_fee_change_denominator: Some(48),
318
319 min_block_gas_cost: Some(0),
320 max_block_gas_cost: Some(10_000_000),
321 block_gas_cost_step: Some(500_000),
322 }
323 }
324}
325
326#[derive(Debug, Serialize, Deserialize, Eq, PartialEq, Clone)]
330#[serde(rename_all = "camelCase")]
331pub struct ContractDeployerAllowListConfig {
332 #[serde(rename = "adminAddresses", skip_serializing_if = "Option::is_none")]
333 pub allow_list_admins: Option<Vec<String>>,
334
335 #[serde(skip_serializing_if = "Option::is_none")]
337 pub block_timestamp: Option<u64>,
338 #[serde(skip_serializing_if = "Option::is_none")]
340 pub disable: Option<bool>,
341}
342
343impl Default for ContractDeployerAllowListConfig {
344 fn default() -> Self {
345 Self {
346 allow_list_admins: None,
347 block_timestamp: Some(0),
348 disable: None,
349 }
350 }
351}
352
353#[derive(Debug, Serialize, Deserialize, Eq, PartialEq, Clone)]
357#[serde(rename_all = "camelCase")]
358pub struct ContractNativeMinterConfig {
359 #[serde(rename = "adminAddresses", skip_serializing_if = "Option::is_none")]
360 pub allow_list_admins: Option<Vec<String>>,
361
362 #[serde(skip_serializing_if = "Option::is_none")]
364 pub block_timestamp: Option<u64>,
365
366 #[serde(skip_serializing_if = "Option::is_none")]
368 pub disable: Option<bool>,
369}
370
371impl Default for ContractNativeMinterConfig {
372 fn default() -> Self {
373 Self {
374 allow_list_admins: None,
375 block_timestamp: Some(0),
376 disable: None,
377 }
378 }
379}
380
381#[derive(Debug, Serialize, Deserialize, Eq, PartialEq, Clone)]
385#[serde(rename_all = "camelCase")]
386pub struct TxAllowListConfig {
387 #[serde(rename = "adminAddresses", skip_serializing_if = "Option::is_none")]
388 pub allow_list_admins: Option<Vec<String>>,
389
390 #[serde(skip_serializing_if = "Option::is_none")]
392 pub block_timestamp: Option<u64>,
393 #[serde(skip_serializing_if = "Option::is_none")]
395 pub disable: Option<bool>,
396}
397
398impl Default for TxAllowListConfig {
399 fn default() -> Self {
400 Self {
401 allow_list_admins: None,
402 block_timestamp: Some(0),
403 disable: None,
404 }
405 }
406}
407
408#[derive(Debug, Serialize, Deserialize, Eq, PartialEq, Clone)]
412#[serde(rename_all = "camelCase")]
413pub struct FeeManagerConfig {
414 #[serde(rename = "adminAddresses", skip_serializing_if = "Option::is_none")]
415 pub allow_list_admins: Option<Vec<String>>,
416
417 #[serde(skip_serializing_if = "Option::is_none")]
419 pub block_timestamp: Option<u64>,
420 #[serde(skip_serializing_if = "Option::is_none")]
422 pub disable: Option<bool>,
423}
424
425impl Default for FeeManagerConfig {
426 fn default() -> Self {
427 Self {
428 allow_list_admins: None,
429 block_timestamp: Some(0),
430 disable: None,
431 }
432 }
433}
434
435#[derive(Debug, Serialize, Deserialize, Eq, PartialEq, Clone)]
438#[serde(rename_all = "camelCase")]
439pub struct AllocAccount {
440 #[serde(skip_serializing_if = "Option::is_none")]
441 pub code: Option<String>,
442 #[serde(skip_serializing_if = "Option::is_none")]
443 pub storage: Option<BTreeMap<String, String>>,
444
445 #[serde(with = "crate::codec::serde::hex_0x_primitive_types_u256")]
446 pub balance: primitive_types::U256,
447
448 #[serde(skip_serializing_if = "Option::is_none")]
450 pub mcbalance: Option<BTreeMap<String, u64>>,
451 #[serde(skip_serializing_if = "Option::is_none")]
452 pub nonce: Option<u64>,
453}
454
455impl Default for AllocAccount {
456 fn default() -> Self {
457 Self {
458 code: None,
459 storage: None,
460 balance: primitive_types::U256::from_str_radix(DEFAULT_INITIAL_AMOUNT, 16).unwrap(),
461 mcbalance: None,
462 nonce: None,
463 }
464 }
465}
466
467#[test]
469fn test_parse() {
470 let _ = env_logger::builder().is_test(true).try_init();
471
472 let resp: Genesis = serde_json::from_str(
474 r#"
475{
476 "unknown1": "field1",
477 "unknown2": "field2",
478
479 "config": {
480 "chainId": 2000777,
481 "homesteadBlock": 0,
482 "eip150Block": 0,
483 "eip150Hash": "0x2086799aeebeae135c246c65021c82b4e15a2c451340993aacfd2751886514f0",
484 "eip155Block": 0,
485 "eip158Block": 0,
486 "byzantiumBlock": 0,
487 "constantinopleBlock": 0,
488 "petersburgBlock": 0,
489 "istanbulBlock": 0,
490 "muirGlacierBlock": 0,
491 "subnetEVMTimestamp": 0,
492 "feeConfig": {
493 "gasLimit": 20000000,
494 "minBaseFee": 1000000000,
495 "targetGas": 100000000,
496 "baseFeeChangeDenominator": 48,
497 "minBlockGasCost": 0,
498 "maxBlockGasCost": 10000000,
499 "targetBlockRate": 2,
500 "blockGasCostStep": 500000
501 }
502 },
503 "alloc": {
504 "6f0f6DA1852857d7789f68a28bba866671f3880D": {
505 "balance": "0x204FCE5E3E25026110000000"
506 }
507 },
508 "nonce": "0x0",
509 "timestamp": "0x0",
510 "extraData": "0x00",
511 "gasLimit": "0x1312D00",
512 "difficulty": "0x0",
513 "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
514 "coinbase": "0x0000000000000000000000000000000000000000",
515 "number": "0x0",
516 "gasUsed": "0x0",
517 "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000"
518}
519"#,
520 )
521 .unwrap();
522
523 let expected = Genesis::default();
524 assert_eq!(resp, expected);
525
526 let d = Genesis::default();
527 let d = d.encode_json().unwrap();
528 log::info!("{}", d);
529}