1use std::{
2 collections::BTreeMap,
3 fs::{self, File},
4 io::{self, Error, ErrorKind, Write},
5 path::Path,
6 string::String,
7};
8
9use log::info;
10use num_bigint::BigInt;
11use serde::{Deserialize, Serialize};
12
13#[derive(Debug, Serialize, Deserialize, Eq, PartialEq, Clone)]
16#[serde(rename_all = "camelCase")]
17pub struct Genesis {
18 #[serde(skip_serializing_if = "Option::is_none")]
19 pub config: Option<ChainConfig>,
20
21 #[serde(with = "big_num_manager::serde_format::big_int_hex")]
22 pub nonce: BigInt,
23 #[serde(with = "big_num_manager::serde_format::big_int_hex")]
24 pub timestamp: BigInt,
25
26 #[serde(skip_serializing_if = "Option::is_none")]
27 pub extra_data: Option<String>,
28
29 #[serde(with = "big_num_manager::serde_format::big_int_hex")]
32 pub gas_limit: BigInt,
33 #[serde(with = "big_num_manager::serde_format::big_int_hex")]
34 pub difficulty: BigInt,
35
36 #[serde(skip_serializing_if = "Option::is_none")]
37 pub mix_hash: Option<String>,
38 #[serde(skip_serializing_if = "Option::is_none")]
39 pub coinbase: Option<String>,
40
41 #[serde(skip_serializing_if = "Option::is_none")]
44 pub alloc: Option<BTreeMap<String, AllocAccount>>,
45
46 #[serde(skip_serializing_if = "Option::is_none")]
48 pub airdrop_hash: Option<String>,
49 #[serde(skip_serializing_if = "Option::is_none")]
50 pub airdrop_amount: Option<String>,
51
52 #[serde(with = "big_num_manager::serde_format::big_int_hex")]
53 pub number: BigInt,
54 #[serde(with = "big_num_manager::serde_format::big_int_hex")]
55 pub gas_used: BigInt,
56
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 = "0x52B7D2DCC80CD2E4000000";
69
70impl Default for Genesis {
71 fn default() -> Self {
72 Self::default()
73 }
74}
75
76impl Genesis {
77 pub fn default() -> Self {
78 let mut alloc = BTreeMap::new();
79 alloc.insert(
80 String::from("6f0f6DA1852857d7789f68a28bba866671f3880D"),
82 AllocAccount::default(),
83 );
84 Self {
85 config: Some(ChainConfig::default()),
86
87 nonce: BigInt::default(),
88 timestamp: BigInt::default(),
89 extra_data: Some(String::from("0x00")),
90
91 gas_limit: big_num_manager::from_hex_to_big_int("0x1312D00")
92 .expect("failed to parse big_int"),
93
94 difficulty: BigInt::default(),
95 mix_hash: Some(String::from(
96 "0x0000000000000000000000000000000000000000000000000000000000000000",
97 )),
98 coinbase: Some(String::from("0x0000000000000000000000000000000000000000")),
99
100 alloc: Some(alloc),
101
102 airdrop_hash: None,
103 airdrop_amount: None,
104
105 number: BigInt::default(),
106 gas_used: BigInt::default(),
107 parent_hash: Some(String::from(
108 "0x0000000000000000000000000000000000000000000000000000000000000000",
109 )),
110 base_fee: None,
111 }
112 }
113
114 pub fn encode_json(&self) -> io::Result<String> {
115 match serde_json::to_string(&self) {
116 Ok(s) => Ok(s),
117 Err(e) => Err(Error::new(
118 ErrorKind::Other,
119 format!("failed to serialize to JSON {}", e),
120 )),
121 }
122 }
123
124 pub fn sync(&self, file_path: &str) -> io::Result<()> {
127 info!("syncing Genesis to '{}'", file_path);
128 let path = Path::new(file_path);
129 let parent_dir = path.parent().expect("unexpected None parent");
130 fs::create_dir_all(parent_dir)?;
131
132 let ret = serde_json::to_vec(self);
133 let d = match ret {
134 Ok(d) => d,
135 Err(e) => {
136 return Err(Error::new(
137 ErrorKind::Other,
138 format!("failed to serialize Genesis to YAML {}", e),
139 ));
140 }
141 };
142 let mut f = File::create(file_path)?;
143 f.write_all(&d)?;
144
145 Ok(())
146 }
147}
148
149#[derive(Debug, Serialize, Deserialize, Eq, PartialEq, Clone)]
151#[serde(rename_all = "camelCase")]
152pub struct ChainConfig {
153 #[serde(skip_serializing_if = "Option::is_none")]
154 pub chain_id: Option<u64>,
155
156 #[serde(skip_serializing_if = "Option::is_none")]
157 pub homestead_block: Option<u64>,
158
159 #[serde(skip_serializing_if = "Option::is_none")]
160 pub eip150_block: Option<u64>,
161 #[serde(skip_serializing_if = "Option::is_none")]
162 pub eip150_hash: Option<String>,
163
164 #[serde(skip_serializing_if = "Option::is_none")]
165 pub eip155_block: Option<u64>,
166 #[serde(skip_serializing_if = "Option::is_none")]
167 pub eip158_block: Option<u64>,
168
169 #[serde(skip_serializing_if = "Option::is_none")]
170 pub byzantium_block: Option<u64>,
171 #[serde(skip_serializing_if = "Option::is_none")]
172 pub constantinople_block: Option<u64>,
173 #[serde(skip_serializing_if = "Option::is_none")]
174 pub petersburg_block: Option<u64>,
175 #[serde(skip_serializing_if = "Option::is_none")]
176 pub istanbul_block: Option<u64>,
177 #[serde(skip_serializing_if = "Option::is_none")]
178 pub muir_glacier_block: Option<u64>,
179
180 #[serde(rename = "subnetEVMTimestamp", skip_serializing_if = "Option::is_none")]
181 pub subnet_evm_timestamp: Option<u64>,
182
183 #[serde(skip_serializing_if = "Option::is_none")]
184 pub fee_config: Option<FeeConfig>,
185 #[serde(skip_serializing_if = "Option::is_none")]
186 pub allow_fee_recipients: Option<bool>,
187
188 #[serde(skip_serializing_if = "Option::is_none")]
189 pub contract_deployer_allow_list_config: Option<ContractDeployerAllowListConfig>,
190}
191
192impl Default for ChainConfig {
193 fn default() -> Self {
194 Self::default()
195 }
196}
197
198impl ChainConfig {
199 pub fn default() -> Self {
200 Self {
201 chain_id: Some(2000777),
205 homestead_block: Some(0),
206
207 eip150_block: Some(0),
208 eip150_hash: Some(String::from(
209 "0x2086799aeebeae135c246c65021c82b4e15a2c451340993aacfd2751886514f0",
210 )),
211
212 eip155_block: Some(0),
213 eip158_block: Some(0),
214
215 byzantium_block: Some(0),
216 constantinople_block: Some(0),
217 petersburg_block: Some(0),
218 istanbul_block: Some(0),
219 muir_glacier_block: Some(0),
220
221 subnet_evm_timestamp: Some(0),
222
223 fee_config: Some(FeeConfig::default()),
224 allow_fee_recipients: None,
225
226 contract_deployer_allow_list_config: Some(ContractDeployerAllowListConfig::default()),
227 }
228 }
229}
230
231#[derive(Debug, Serialize, Deserialize, Eq, PartialEq, Clone)]
233#[serde(rename_all = "camelCase")]
234pub struct FeeConfig {
235 #[serde(skip_serializing_if = "Option::is_none")]
238 pub gas_limit: Option<u64>,
239 #[serde(skip_serializing_if = "Option::is_none")]
240 pub target_block_rate: Option<u64>,
241
242 #[serde(skip_serializing_if = "Option::is_none")]
243 pub min_base_fee: Option<u64>,
244 #[serde(skip_serializing_if = "Option::is_none")]
245 pub target_gas: Option<u64>,
246 #[serde(skip_serializing_if = "Option::is_none")]
247 pub base_fee_change_denominator: Option<u64>,
248
249 #[serde(skip_serializing_if = "Option::is_none")]
250 pub min_block_gas_cost: Option<u64>,
251 #[serde(skip_serializing_if = "Option::is_none")]
252 pub max_block_gas_cost: Option<u64>,
253 #[serde(skip_serializing_if = "Option::is_none")]
254 pub block_gas_cost_step: Option<u64>,
255}
256
257impl Default for FeeConfig {
258 fn default() -> Self {
259 Self::default()
260 }
261}
262
263impl FeeConfig {
264 pub fn default() -> Self {
265 Self {
266 gas_limit: Some(20000000),
267 target_block_rate: Some(2),
268
269 min_base_fee: Some(1000000000),
270 target_gas: Some(100000000),
271 base_fee_change_denominator: Some(48),
272
273 min_block_gas_cost: Some(0),
274 max_block_gas_cost: Some(10000000),
275 block_gas_cost_step: Some(500000),
276 }
277 }
278}
279
280#[derive(Debug, Serialize, Deserialize, Eq, PartialEq, Clone)]
282#[serde(rename_all = "camelCase")]
283pub struct ContractDeployerAllowListConfig {
284 #[serde(skip_serializing_if = "Option::is_none")]
285 pub block_timestamp: Option<u64>,
286 #[serde(rename = "adminAddresses", skip_serializing_if = "Option::is_none")]
287 pub allow_list_admins: Option<Vec<String>>,
288}
289
290impl Default for ContractDeployerAllowListConfig {
291 fn default() -> Self {
292 Self::default()
293 }
294}
295
296impl ContractDeployerAllowListConfig {
297 pub fn default() -> Self {
298 Self {
299 block_timestamp: Some(0),
300 allow_list_admins: None,
301 }
302 }
303}
304
305#[derive(Debug, Serialize, Deserialize, Eq, PartialEq, Clone)]
308#[serde(rename_all = "camelCase")]
309pub struct AllocAccount {
310 #[serde(skip_serializing_if = "Option::is_none")]
311 pub code: Option<String>,
312 #[serde(skip_serializing_if = "Option::is_none")]
313 pub storage: Option<BTreeMap<String, String>>,
314
315 #[serde(with = "big_num_manager::serde_format::big_int_hex")]
316 pub balance: BigInt,
317
318 #[serde(skip_serializing_if = "Option::is_none")]
320 pub mcbalance: Option<BTreeMap<String, u64>>,
321 #[serde(skip_serializing_if = "Option::is_none")]
322 pub nonce: Option<u64>,
323}
324
325impl Default for AllocAccount {
326 fn default() -> Self {
327 Self::default()
328 }
329}
330
331impl AllocAccount {
332 pub fn default() -> Self {
333 Self {
334 code: None,
335 storage: None,
336 balance: big_num_manager::from_hex_to_big_int(DEFAULT_INITIAL_AMOUNT)
337 .expect("failed to parse initial amount"),
338 mcbalance: None,
339 nonce: None,
340 }
341 }
342}
343
344#[test]
345fn test_parse() {
346 let _ = env_logger::builder().is_test(true).try_init();
347
348 let resp: Genesis = serde_json::from_str(
350 r#"
351{
352 "config": {
353 "chainId": 2000777,
354 "homesteadBlock": 0,
355 "eip150Block": 0,
356 "eip150Hash": "0x2086799aeebeae135c246c65021c82b4e15a2c451340993aacfd2751886514f0",
357 "eip155Block": 0,
358 "eip158Block": 0,
359 "byzantiumBlock": 0,
360 "constantinopleBlock": 0,
361 "petersburgBlock": 0,
362 "istanbulBlock": 0,
363 "muirGlacierBlock": 0,
364 "subnetEVMTimestamp": 0,
365 "feeConfig": {
366 "gasLimit": 20000000,
367 "minBaseFee": 1000000000,
368 "targetGas": 100000000,
369 "baseFeeChangeDenominator": 48,
370 "minBlockGasCost": 0,
371 "maxBlockGasCost": 10000000,
372 "targetBlockRate": 2,
373 "blockGasCostStep": 500000
374 },
375 "contractDeployerAllowListConfig": { "blockTimestamp": 0 }
376 },
377 "alloc": {
378 "6f0f6DA1852857d7789f68a28bba866671f3880D": {
379 "balance": "0x52B7D2DCC80CD2E4000000"
380 }
381 },
382 "nonce": "0x0",
383 "timestamp": "0x0",
384 "extraData": "0x00",
385 "gasLimit": "0x1312D00",
386 "difficulty": "0x0",
387 "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
388 "coinbase": "0x0000000000000000000000000000000000000000",
389 "number": "0x0",
390 "gasUsed": "0x0",
391 "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000"
392}
393"#,
394 )
395 .unwrap();
396
397 let expected = Genesis::default();
398 assert_eq!(resp, expected);
399
400 let d = Genesis::default();
401 let d = d.encode_json().unwrap();
402 info!("{}", d);
403}