Skip to main content

avalanche_types/xsvm/
genesis.rs

1use std::{
2    fs::{self, File},
3    io::{self, Error, ErrorKind, Write},
4    path::Path,
5    str::FromStr,
6};
7
8use crate::{errors, ids::short, key, packer};
9use serde::{Deserialize, Serialize};
10
11/// ref. <https://github.com/ava-labs/xsvm/blob/master/genesis/genesis.go>
12#[derive(Debug, Serialize, Deserialize, Eq, PartialEq, Clone)]
13#[serde(rename_all = "camelCase")]
14pub struct Genesis {
15    #[serde(default)]
16    pub timestamp: i64,
17    #[serde(skip_serializing_if = "Option::is_none")]
18    pub allocations: Option<Vec<Allocation>>,
19}
20
21impl Default for Genesis {
22    fn default() -> Self {
23        Self {
24            timestamp: 0,
25            allocations: Some(vec![Allocation {
26                // ref. https://github.com/ava-labs/subnet-evm/blob/master/networks/11111/genesis.json
27                address: short::Id::from_str("6Y3kysjF9jnHnYkdS9yGAuoHyae2eNmeV").unwrap(),
28                balance: DEFAULT_INITIAL_AMOUNT,
29            }]),
30        }
31    }
32}
33
34pub const CODEC_VERSION: u16 = 0;
35pub const DEFAULT_INITIAL_AMOUNT: u64 = 1000000000;
36
37impl Genesis {
38    /// Creates a new Genesis object with "keys" number of generated pre-funded keys.
39    pub fn new<T: key::secp256k1::ReadOnly>(seed_keys: &[T]) -> io::Result<Self> {
40        // maximize total supply
41        let max_total_alloc = u64::MAX;
42        let total_keys = seed_keys.len();
43        let alloc_per_key = if total_keys > 0 {
44            max_total_alloc / total_keys as u64
45        } else {
46            0u64
47        };
48        // divide by 2, allow more room for transfers
49        let alloc_per_key = alloc_per_key / 2;
50
51        let mut allocs = Vec::new();
52        for k in seed_keys.iter() {
53            allocs.push(Allocation {
54                address: k.short_address().unwrap(),
55                balance: alloc_per_key,
56            });
57        }
58
59        let genesis = Self {
60            allocations: Some(allocs),
61            ..Self::default()
62        };
63
64        Ok(genesis)
65    }
66
67    pub fn encode_json(&self) -> io::Result<String> {
68        serde_json::to_string(&self)
69            .map_err(|e| Error::new(ErrorKind::Other, format!("failed to serialize JSON {}", e)))
70    }
71
72    /// Encodes the genesis to JSON bytes.
73    pub fn to_json_bytes(&self) -> io::Result<Vec<u8>> {
74        serde_json::to_vec(self)
75            .map_err(|e| Error::new(ErrorKind::Other, format!("failed encode JSON {}", e)))
76    }
77
78    /// Saves the current genesis to disk
79    /// and overwrites the file in JSON format.
80    pub fn sync_json(&self, file_path: &str) -> io::Result<()> {
81        log::info!("syncing '{}' in JSON", file_path);
82        let path = Path::new(file_path);
83        if let Some(parent_dir) = path.parent() {
84            log::info!("creating parent dir '{}'", parent_dir.display());
85            fs::create_dir_all(parent_dir)?;
86        }
87
88        let d = self.to_json_bytes()?;
89
90        let mut f = File::create(file_path)?;
91        f.write_all(&d)?;
92
93        Ok(())
94    }
95
96    /// Encodes the genesis to packer bytes.
97    /// ref. <https://pkg.go.dev/github.com/ava-labs/avalanchego/utils/wrappers#Packer>
98    /// ref. <https://pkg.go.dev/github.com/ava-labs/avalanchego/codec#Manager>
99    pub fn to_packer_bytes(&self) -> errors::Result<Vec<u8>> {
100        let packer = packer::Packer::new((1 << 31) - 1, 128);
101
102        // codec version
103        // ref. "avalanchego/codec.manager.Marshal"
104        packer.pack_u16(CODEC_VERSION)?;
105        packer.pack_u64(self.timestamp as u64)?;
106
107        if let Some(allocs) = &self.allocations {
108            packer.pack_u32(allocs.len() as u32)?;
109            for alloc in allocs.iter() {
110                packer.pack_bytes(alloc.address.as_ref())?;
111                packer.pack_u64(alloc.balance)?;
112            }
113        }
114
115        Ok(packer.take_bytes().into())
116    }
117}
118
119/// ref. <https://pkg.go.dev/github.com/ava-labs/subnet-evm/core#GenesisAlloc>
120/// ref. <https://pkg.go.dev/github.com/ava-labs/subnet-evm/core#GenesisAccount>
121#[derive(Debug, Serialize, Deserialize, Eq, PartialEq, Clone)]
122#[serde(rename_all = "camelCase")]
123pub struct Allocation {
124    pub address: short::Id,
125    #[serde(default)]
126    pub balance: u64,
127}
128
129impl Default for Allocation {
130    fn default() -> Self {
131        Self {
132            address: short::Id::empty(),
133            balance: 0,
134        }
135    }
136}
137
138/// RUST_LOG=debug cargo test --package avalanche-types --lib --features="evm" -- xsvm::genesis::test_encode_packer_bytes --exact --show-output
139#[test]
140fn test_encode_packer_bytes() {
141    let _ = env_logger::builder().is_test(true).try_init();
142
143    let genesis = Genesis {
144        timestamp: 123,
145        allocations: Some(vec![
146            Allocation {
147                // ref. https://github.com/ava-labs/subnet-evm/blob/master/networks/11111/genesis.json
148                address: short::Id::from_str("6Y3kysjF9jnHnYkdS9yGAuoHyae2eNmeV").unwrap(),
149                balance: 1000000000,
150            },
151            Allocation {
152                // ref. https://github.com/ava-labs/subnet-evm/blob/master/networks/11111/genesis.json
153                address: short::Id::from_str("LeKrndtsMxcLMzHz3w4uo1XtLDpfi66c").unwrap(),
154                balance: 3000000000,
155            },
156        ]),
157    };
158    let genesis_packer_bytes = genesis.to_packer_bytes().unwrap();
159
160    let expected = vec![
161        0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7b, 0x0, 0x0, 0x0, 0x2, 0x3c, 0xb7, 0xd3,
162        0x84, 0x2e, 0x8c, 0xee, 0x6a, 0xe, 0xbd, 0x9, 0xf1, 0xfe, 0x88, 0x4f, 0x68, 0x61, 0xe1,
163        0xb2, 0x9c, 0x0, 0x0, 0x0, 0x0, 0x3b, 0x9a, 0xca, 0x0, 0x3, 0xb7, 0xf, 0x8c, 0x60, 0x6b,
164        0x2b, 0xf, 0x99, 0x84, 0x9d, 0xc8, 0x5c, 0x40, 0xf, 0xd1, 0x7e, 0xfe, 0x1f, 0x60, 0x0, 0x0,
165        0x0, 0x0, 0xb2, 0xd0, 0x5e, 0x0,
166    ];
167    assert_eq!(genesis_packer_bytes, expected);
168}
169
170/// RUST_LOG=debug cargo test --package avalanche-types --lib -- xsvm::genesis::test_parse --exact --show-output
171#[test]
172fn test_parse() {
173    let _ = env_logger::builder().is_test(true).try_init();
174
175    // ref. https://github.com/ava-labs/subnet-evm/blob/master/networks/11111/genesis.json
176    let resp: Genesis = serde_json::from_str(
177        r#"
178{
179    "unknown1": "field1",
180    "unknown2": "field2",
181
182    "timestamp": 123,
183
184        "allocations": [
185            {"address": "6Y3kysjF9jnHnYkdS9yGAuoHyae2eNmeV", "balance": 2000000000},
186            {"address": "LeKrndtsMxcLMzHz3w4uo1XtLDpfi66c", "balance": 3000000000}
187        ]
188}
189"#,
190    )
191    .unwrap();
192
193    let expected = Genesis {
194        timestamp: 123,
195        allocations: Some(vec![
196            Allocation {
197                // ref. https://github.com/ava-labs/subnet-evm/blob/master/networks/11111/genesis.json
198                address: short::Id::from_str("6Y3kysjF9jnHnYkdS9yGAuoHyae2eNmeV").unwrap(),
199                balance: 2000000000,
200            },
201            Allocation {
202                // ref. https://github.com/ava-labs/subnet-evm/blob/master/networks/11111/genesis.json
203                address: short::Id::from_str("LeKrndtsMxcLMzHz3w4uo1XtLDpfi66c").unwrap(),
204                balance: 3000000000,
205            },
206        ]),
207    };
208    assert_eq!(resp, expected);
209
210    let d = expected.encode_json().unwrap();
211    log::info!("{}", d);
212}