subnet_evm/
genesis.rs

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/// ref. https://pkg.go.dev/github.com/ava-labs/subnet-evm/core#Genesis
14/// ref. https://pkg.go.dev/github.com/ava-labs/subnet-evm/params#ChainConfig
15#[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    /// Make sure this is set equal to "ChainConfig.FeeConfig.gas_limit".
30    /// ref. https://github.com/ava-labs/subnet-evm/pull/63
31    #[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    /// MUST BE ordered by its key in order for all nodes to have the same JSON outputs.
42    /// ref. https://doc.rust-lang.org/std/collections/index.html#use-a-btreemap-when
43    #[serde(skip_serializing_if = "Option::is_none")]
44    pub alloc: Option<BTreeMap<String, AllocAccount>>,
45
46    /// WARNING: Big airdrop data may cause OOM in subnet-evm.
47    #[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
63/// On the X-Chain, one AVAX is 10^9  units.
64/// On the P-Chain, one AVAX is 10^9  units.
65/// On the C-Chain, one AVAX is 10^18 units.
66/// "0x52B7D2DCC80CD2E4000000" is "100000000000000000000000000" (100,000,000 AVAX).
67/// ref. https://www.rapidtables.com/convert/number/hex-to-decimal.html
68pub 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            // ref. https://github.com/ava-labs/subnet-evm/blob/master/networks/11111/genesis.json
81            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    /// Saves the current anchor node to disk
125    /// and overwrites the file.
126    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/// ref. https://pkg.go.dev/github.com/ava-labs/subnet-evm/params#ChainConfig
150#[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            // don't use local ID "43112" to avoid config override
202            // ref. https://github.com/ava-labs/coreth/blob/v0.8.6/plugin/evm/vm.go#L326-L328
203            // ref. https://github.com/ava-labs/avalanche-ops/issues/8
204            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/// ref. https://pkg.go.dev/github.com/ava-labs/subnet-evm/params#FeeConfig
232#[derive(Debug, Serialize, Deserialize, Eq, PartialEq, Clone)]
233#[serde(rename_all = "camelCase")]
234pub struct FeeConfig {
235    /// Make sure this is set equal to "Genesis.gas_limit".
236    /// ref. https://github.com/ava-labs/subnet-evm/pull/63
237    #[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/// ref. https://github.com/ava-labs/subnet-evm/blob/master/precompile/contract_deployer_allow_list.go
281#[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/// ref. https://pkg.go.dev/github.com/ava-labs/subnet-evm/core#GenesisAlloc
306/// ref. https://pkg.go.dev/github.com/ava-labs/subnet-evm/core#GenesisAccount
307#[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    /// ref. https://pkg.go.dev/github.com/ava-labs/subnet-evm/core#GenesisMultiCoinBalance
319    #[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    // ref. https://github.com/ava-labs/subnet-evm/blob/master/networks/11111/genesis.json
349    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}