Skip to main content

avalanche_types/subnet_evm/
genesis.rs

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/// ref. <https://pkg.go.dev/github.com/ava-labs/subnet-evm/core#Genesis>
11/// ref. <https://pkg.go.dev/github.com/ava-labs/subnet-evm/params#ChainConfig>
12#[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    /// Make sure this is set equal to "ChainConfig.FeeConfig.gas_limit".
27    /// ref. <https://github.com/ava-labs/subnet-evm/pull/63>
28    ///
29    /// Use <https://www.rapidtables.com/convert/number/decimal-to-hex.html> to convert.
30    #[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    /// MUST BE ordered by its key in order for all nodes to have the same JSON outputs.
41    /// And expressed as hex strings with the canonical 0x prefix.
42    /// ref. <https://doc.rust-lang.org/std/collections/index.html#use-a-btreemap-when>
43    /// ref. <https://docs.avax.network/subnets/customize-a-subnet#setting-the-genesis-allocation>
44    #[serde(skip_serializing_if = "Option::is_none")]
45    pub alloc: Option<BTreeMap<String, AllocAccount>>,
46
47    /// WARNING: Big airdrop data may cause OOM in subnet-evm.
48    #[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
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/// "0x204FCE5E3E25026110000000" is "10000000000000000000000000000" (10,000,000,000 AVAX).
67/// ref. <https://www.rapidtables.com/convert/number/decimal-to-hex.html>
68/// ref. <https://www.rapidtables.com/convert/number/hex-to-decimal.html>
69pub 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            // ref. https://github.com/ava-labs/subnet-evm/blob/master/networks/11111/genesis.json
76            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            // ref. https://www.rapidtables.com/convert/number/decimal-to-hex.html
87            // ref. https://www.rapidtables.com/convert/number/hex-to-decimal.html
88            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    /// Creates a new Genesis object with "keys" number of generated pre-funded keys.
113    pub fn new(seed_eth_addrs: Vec<String>) -> io::Result<Self> {
114        // maximize total supply
115        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        // divide by 2, allow more room for transfers
123        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    /// Encodes the genesis to bytes.
152    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    /// Saves the current genesis to disk
158    /// and overwrites the file.
159    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/// ref. <https://pkg.go.dev/github.com/ava-labs/subnet-evm/params#ChainConfig>
178/// ref. <https://pkg.go.dev/github.com/ava-labs/subnet-evm/params#pkg-variables>
179#[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    /// ref. <https://docs.avax.network/subnets/customize-a-subnet>
218    #[serde(skip_serializing_if = "Option::is_none")]
219    pub contract_deployer_allow_list_config: Option<ContractDeployerAllowListConfig>,
220    /// ref. <https://docs.avax.network/subnets/customize-a-subnet>
221    #[serde(skip_serializing_if = "Option::is_none")]
222    pub contract_native_minter_config: Option<ContractNativeMinterConfig>,
223    /// ref. <https://docs.avax.network/subnets/customize-a-subnet>
224    #[serde(skip_serializing_if = "Option::is_none")]
225    pub tx_allow_list_config: Option<TxAllowListConfig>,
226    /// ref. <https://docs.avax.network/subnets/customize-a-subnet>
227    #[serde(skip_serializing_if = "Option::is_none")]
228    pub fee_manager_config: Option<FeeManagerConfig>,
229}
230
231impl Default for ChainConfig {
232    /// ref. <https://pkg.go.dev/github.com/ava-labs/subnet-evm/params#pkg-variables>
233    fn default() -> Self {
234        Self {
235            // don't use local ID "43112" to avoid config override
236            // ref. <https://github.com/ava-labs/coreth/blob/v0.8.6/plugin/evm/vm.go#L326-L328>
237            // ref. <https://github.com/ava-labs/avalanche-ops/issues/8>
238            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/// ref. <https://pkg.go.dev/github.com/ava-labs/subnet-evm/commontype#FeeConfig>
270/// ref. <https://pkg.go.dev/github.com/ava-labs/subnet-evm/params#pkg-variables>
271#[derive(Debug, Serialize, Deserialize, Eq, PartialEq, Clone)]
272#[serde(rename_all = "camelCase")]
273pub struct FeeConfig {
274    /// Make sure this is set equal to "Genesis.gas_limit".
275    /// ref. <https://github.com/ava-labs/subnet-evm/pull/63>
276    #[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    /// USE WITH CAUTION!
289    /// Set min/max block gas cost the same and low to
290    /// keep the gas price constant (useful for load tests).
291    #[serde(skip_serializing_if = "Option::is_none")]
292    pub min_block_gas_cost: Option<u64>,
293    /// USE WITH CAUTION!
294    /// Set min/max block gas cost the same and low to
295    /// keep the gas price constant (useful for load tests).
296    #[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    /// ref. <https://github.com/ava-labs/public-chain-assets/blob/main/chains/53935/genesis.json>
305    /// ref. <https://pkg.go.dev/github.com/ava-labs/subnet-evm/params#pkg-variables>
306    /// ref. <https://github.com/ava-labs/subnet-evm/blob/master/scripts/run.sh>
307    /// ref. <https://www.rapidtables.com/convert/number/decimal-to-hex.html>
308    /// ref. <https://www.rapidtables.com/convert/number/hex-to-decimal.html>
309    fn default() -> Self {
310        Self {
311            // ref. <https://github.com/ava-labs/subnet-evm/blob/master/scripts/run.sh>
312            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/// ref. <https://github.com/ava-labs/subnet-evm/blob/master/precompile/contract_deployer_allow_list.go>
327/// ref. <https://github.com/ava-labs/subnet-evm/blob/master/precompile/upgradeable.go>
328/// ref. <https://github.com/ava-labs/subnet-evm/blob/master/params/precompile_config.go>
329#[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    /// Timestamp for the upgrade.
336    #[serde(skip_serializing_if = "Option::is_none")]
337    pub block_timestamp: Option<u64>,
338    /// Set to "true" for the upgrade to deactivate the precompile and reset its storage.
339    #[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/// ref. <https://github.com/ava-labs/subnet-evm/blob/master/precompile/contract_native_minter.go>
354/// ref. <https://github.com/ava-labs/subnet-evm/blob/master/precompile/upgradeable.go>
355/// ref. <https://github.com/ava-labs/subnet-evm/blob/master/params/precompile_config.go>
356#[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    /// Timestamp for the upgrade.
363    #[serde(skip_serializing_if = "Option::is_none")]
364    pub block_timestamp: Option<u64>,
365
366    /// Set to "true" for the upgrade to deactivate the precompile and reset its storage.
367    #[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/// ref. <https://github.com/ava-labs/subnet-evm/blob/master/precompile/tx_allow_list.go>
382/// ref. <https://github.com/ava-labs/subnet-evm/blob/master/precompile/upgradeable.go>
383/// ref. <https://github.com/ava-labs/subnet-evm/blob/master/params/precompile_config.go>
384#[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    /// Timestamp for the upgrade.
391    #[serde(skip_serializing_if = "Option::is_none")]
392    pub block_timestamp: Option<u64>,
393    /// Set to "true" for the upgrade to deactivate the precompile and reset its storage.
394    #[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/// ref. <https://github.com/ava-labs/subnet-evm/blob/master/precompile/fee_config_manager.go>
409/// ref. <https://github.com/ava-labs/subnet-evm/blob/master/precompile/upgradeable.go>
410/// ref. <https://github.com/ava-labs/subnet-evm/blob/master/params/precompile_config.go>
411#[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    /// Timestamp for the upgrade.
418    #[serde(skip_serializing_if = "Option::is_none")]
419    pub block_timestamp: Option<u64>,
420    /// Set to "true" for the upgrade to deactivate the precompile and reset its storage.
421    #[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/// ref. <https://pkg.go.dev/github.com/ava-labs/subnet-evm/core#GenesisAlloc>
436/// ref. <https://pkg.go.dev/github.com/ava-labs/subnet-evm/core#GenesisAccount>
437#[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    /// ref. <https://pkg.go.dev/github.com/ava-labs/subnet-evm/core#GenesisMultiCoinBalance>
449    #[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/// RUST_LOG=debug cargo test --package avalanche-types --lib -- subnet_evm::genesis::test_parse --exact --show-output
468#[test]
469fn test_parse() {
470    let _ = env_logger::builder().is_test(true).try_init();
471
472    // ref. https://github.com/ava-labs/subnet-evm/blob/master/networks/11111/genesis.json
473    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}