1use anyhow::{Context, Result};
5use fastcrypto::encoding::{Base64, Encoding};
6use fastcrypto::hash::HashFunction;
7use serde::{Deserialize, Deserializer, Serialize, Serializer};
8use std::{fs, path::Path};
9use sui_types::authenticator_state::{AuthenticatorStateInner, get_authenticator_state};
10use sui_types::base_types::{ObjectID, SuiAddress};
11use sui_types::clock::Clock;
12use sui_types::committee::CommitteeWithNetworkMetadata;
13use sui_types::crypto::DefaultHash;
14use sui_types::deny_list_v1::{PerTypeDenyList, get_coin_deny_list};
15use sui_types::effects::{TransactionEffects, TransactionEvents};
16use sui_types::gas_coin::TOTAL_SUPPLY_MIST;
17use sui_types::messages_checkpoint::{
18 CertifiedCheckpointSummary, CheckpointContents, CheckpointSummary, VerifiedCheckpoint,
19};
20use sui_types::storage::ObjectStore;
21use sui_types::sui_system_state::{
22 SuiSystemState, SuiSystemStateTrait, SuiSystemStateWrapper, SuiValidatorGenesis,
23 get_sui_system_state, get_sui_system_state_wrapper,
24};
25use sui_types::transaction::Transaction;
26use sui_types::{SUI_BRIDGE_OBJECT_ID, SUI_RANDOMNESS_STATE_OBJECT_ID};
27use sui_types::{
28 committee::{Committee, EpochId, ProtocolVersion},
29 error::SuiResult,
30 object::Object,
31};
32use tracing::trace;
33
34#[derive(Clone, Debug)]
35pub struct Genesis {
36 checkpoint: CertifiedCheckpointSummary,
37 checkpoint_contents: CheckpointContents,
38 transaction: Transaction,
39 effects: TransactionEffects,
40 events: TransactionEvents,
41 objects: Vec<Object>,
42}
43
44#[derive(Clone, Serialize, Deserialize, PartialEq, Eq, Debug)]
45pub struct UnsignedGenesis {
46 pub checkpoint: CheckpointSummary,
47 pub checkpoint_contents: CheckpointContents,
48 pub transaction: Transaction,
49 pub effects: TransactionEffects,
50 pub events: TransactionEvents,
51 pub objects: Vec<Object>,
52}
53
54impl PartialEq for Genesis {
56 fn eq(&self, other: &Self) -> bool {
57 self.checkpoint.data() == other.checkpoint.data()
58 && {
59 let this = self.checkpoint.auth_sig();
60 let other = other.checkpoint.auth_sig();
61
62 this.epoch == other.epoch
63 && this.signature.as_ref() == other.signature.as_ref()
64 && this.signers_map == other.signers_map
65 }
66 && self.checkpoint_contents == other.checkpoint_contents
67 && self.transaction == other.transaction
68 && self.effects == other.effects
69 && self.objects == other.objects
70 }
71}
72
73impl Eq for Genesis {}
74
75impl Genesis {
76 pub fn new(
77 checkpoint: CertifiedCheckpointSummary,
78 checkpoint_contents: CheckpointContents,
79 transaction: Transaction,
80 effects: TransactionEffects,
81 events: TransactionEvents,
82 objects: Vec<Object>,
83 ) -> Self {
84 Self {
85 checkpoint,
86 checkpoint_contents,
87 transaction,
88 effects,
89 events,
90 objects,
91 }
92 }
93
94 pub fn objects(&self) -> &[Object] {
95 &self.objects
96 }
97
98 pub fn object(&self, id: ObjectID) -> Option<Object> {
99 self.objects.iter().find(|o| o.id() == id).cloned()
100 }
101
102 pub fn transaction(&self) -> &Transaction {
103 &self.transaction
104 }
105
106 pub fn effects(&self) -> &TransactionEffects {
107 &self.effects
108 }
109 pub fn events(&self) -> &TransactionEvents {
110 &self.events
111 }
112
113 pub fn checkpoint(&self) -> VerifiedCheckpoint {
114 self.checkpoint
115 .clone()
116 .try_into_verified(&self.committee().unwrap())
117 .unwrap()
118 }
119
120 pub fn checkpoint_contents(&self) -> &CheckpointContents {
121 &self.checkpoint_contents
122 }
123
124 pub fn epoch(&self) -> EpochId {
125 0
126 }
127
128 pub fn validator_set_for_tooling(&self) -> Vec<SuiValidatorGenesis> {
129 self.sui_system_object()
130 .into_genesis_version_for_tooling()
131 .validators
132 .active_validators
133 }
134
135 pub fn committee_with_network(&self) -> CommitteeWithNetworkMetadata {
136 self.sui_system_object().get_current_epoch_committee()
137 }
138
139 pub fn reference_gas_price(&self) -> u64 {
140 self.sui_system_object().reference_gas_price()
141 }
142
143 pub fn committee(&self) -> SuiResult<Committee> {
145 Ok(self.committee_with_network().committee().clone())
146 }
147
148 pub fn sui_system_wrapper_object(&self) -> SuiSystemStateWrapper {
149 get_sui_system_state_wrapper(&self.objects())
150 .expect("Sui System State Wrapper object must always exist")
151 }
152
153 pub fn sui_system_object(&self) -> SuiSystemState {
154 get_sui_system_state(&self.objects()).expect("Sui System State object must always exist")
155 }
156
157 pub fn clock(&self) -> Clock {
158 let clock = self
159 .objects()
160 .iter()
161 .find(|o| o.id() == sui_types::SUI_CLOCK_OBJECT_ID)
162 .expect("Clock must always exist")
163 .data
164 .try_as_move()
165 .expect("Clock must be a Move object");
166 bcs::from_bytes::<Clock>(clock.contents())
167 .expect("Clock object deserialization cannot fail")
168 }
169
170 pub fn load<P: AsRef<Path>>(path: P) -> Result<Self, anyhow::Error> {
171 let path = path.as_ref();
172 trace!("Reading Genesis from {}", path.display());
173 let bytes = fs::read(path)
174 .with_context(|| format!("Unable to load Genesis from {}", path.display()))?;
175 bcs::from_bytes(&bytes)
176 .with_context(|| format!("Unable to parse Genesis from {}", path.display()))
177 }
178
179 pub fn save<P: AsRef<Path>>(&self, path: P) -> Result<(), anyhow::Error> {
180 let path = path.as_ref();
181 trace!("Writing Genesis to {}", path.display());
182 let bytes = bcs::to_bytes(&self)?;
183 fs::write(path, bytes)
184 .with_context(|| format!("Unable to save Genesis to {}", path.display()))?;
185 Ok(())
186 }
187
188 pub fn to_bytes(&self) -> Vec<u8> {
189 bcs::to_bytes(self).expect("failed to serialize genesis")
190 }
191
192 pub fn hash(&self) -> [u8; 32] {
193 use std::io::Write;
194
195 let mut digest = DefaultHash::default();
196 digest.write_all(&self.to_bytes()).unwrap();
197 let hash = digest.finalize();
198 hash.into()
199 }
200}
201
202impl Serialize for Genesis {
203 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
204 where
205 S: Serializer,
206 {
207 use serde::ser::Error;
208
209 #[derive(Serialize)]
210 struct RawGenesis<'a> {
211 checkpoint: &'a CertifiedCheckpointSummary,
212 checkpoint_contents: &'a CheckpointContents,
213 transaction: &'a Transaction,
214 effects: &'a TransactionEffects,
215 events: &'a TransactionEvents,
216 objects: &'a [Object],
217 }
218
219 let raw_genesis = RawGenesis {
220 checkpoint: &self.checkpoint,
221 checkpoint_contents: &self.checkpoint_contents,
222 transaction: &self.transaction,
223 effects: &self.effects,
224 events: &self.events,
225 objects: &self.objects,
226 };
227
228 let bytes = bcs::to_bytes(&raw_genesis).map_err(|e| Error::custom(e.to_string()))?;
229
230 if serializer.is_human_readable() {
231 let s = Base64::encode(&bytes);
232 serializer.serialize_str(&s)
233 } else {
234 serializer.serialize_bytes(&bytes)
235 }
236 }
237}
238
239impl<'de> Deserialize<'de> for Genesis {
240 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
241 where
242 D: Deserializer<'de>,
243 {
244 use serde::de::Error;
245
246 #[derive(Deserialize)]
247 struct RawGenesis {
248 checkpoint: CertifiedCheckpointSummary,
249 checkpoint_contents: CheckpointContents,
250 transaction: Transaction,
251 effects: TransactionEffects,
252 events: TransactionEvents,
253 objects: Vec<Object>,
254 }
255
256 let bytes = if deserializer.is_human_readable() {
257 let s = String::deserialize(deserializer)?;
258 Base64::decode(&s).map_err(|e| Error::custom(e.to_string()))?
259 } else {
260 let data: Vec<u8> = Vec::deserialize(deserializer)?;
261 data
262 };
263
264 let RawGenesis {
265 checkpoint,
266 checkpoint_contents,
267 transaction,
268 effects,
269 events,
270 objects,
271 } = bcs::from_bytes(&bytes).map_err(|e| Error::custom(e.to_string()))?;
272
273 Ok(Genesis {
274 checkpoint,
275 checkpoint_contents,
276 transaction,
277 effects,
278 events,
279 objects,
280 })
281 }
282}
283
284impl UnsignedGenesis {
285 pub fn objects(&self) -> &[Object] {
286 &self.objects
287 }
288
289 pub fn object(&self, id: ObjectID) -> Option<Object> {
290 self.objects.iter().find(|o| o.id() == id).cloned()
291 }
292
293 pub fn transaction(&self) -> &Transaction {
294 &self.transaction
295 }
296
297 pub fn effects(&self) -> &TransactionEffects {
298 &self.effects
299 }
300 pub fn events(&self) -> &TransactionEvents {
301 &self.events
302 }
303
304 pub fn checkpoint(&self) -> &CheckpointSummary {
305 &self.checkpoint
306 }
307
308 pub fn checkpoint_contents(&self) -> &CheckpointContents {
309 &self.checkpoint_contents
310 }
311
312 pub fn epoch(&self) -> EpochId {
313 0
314 }
315
316 pub fn sui_system_wrapper_object(&self) -> SuiSystemStateWrapper {
317 get_sui_system_state_wrapper(&self.objects())
318 .expect("Sui System State Wrapper object must always exist")
319 }
320
321 pub fn sui_system_object(&self) -> SuiSystemState {
322 get_sui_system_state(&self.objects()).expect("Sui System State object must always exist")
323 }
324
325 pub fn authenticator_state_object(&self) -> Option<AuthenticatorStateInner> {
326 get_authenticator_state(self.objects()).expect("read from genesis cannot fail")
327 }
328
329 pub fn has_randomness_state_object(&self) -> bool {
330 self.objects()
331 .get_object(&SUI_RANDOMNESS_STATE_OBJECT_ID)
332 .is_some()
333 }
334
335 pub fn has_bridge_object(&self) -> bool {
336 self.objects().get_object(&SUI_BRIDGE_OBJECT_ID).is_some()
337 }
338
339 pub fn coin_deny_list_state(&self) -> Option<PerTypeDenyList> {
340 get_coin_deny_list(&self.objects())
341 }
342}
343
344#[derive(Clone, Debug, Serialize, Deserialize)]
345#[serde(rename_all = "kebab-case")]
346pub struct GenesisChainParameters {
347 pub protocol_version: u64,
348 pub chain_start_timestamp_ms: u64,
349 pub epoch_duration_ms: u64,
350
351 pub stake_subsidy_start_epoch: u64,
353 pub stake_subsidy_initial_distribution_amount: u64,
354 pub stake_subsidy_period_length: u64,
355 pub stake_subsidy_decrease_rate: u16,
356
357 pub max_validator_count: u64,
359 pub min_validator_joining_stake: u64,
360 pub validator_low_stake_threshold: u64,
361 pub validator_very_low_stake_threshold: u64,
362 pub validator_low_stake_grace_period: u64,
363}
364
365#[derive(Serialize, Deserialize)]
367pub struct GenesisCeremonyParameters {
368 #[serde(default = "GenesisCeremonyParameters::default_timestamp_ms")]
369 pub chain_start_timestamp_ms: u64,
370
371 #[serde(default = "ProtocolVersion::max")]
373 pub protocol_version: ProtocolVersion,
374
375 #[serde(default = "GenesisCeremonyParameters::default_allow_insertion_of_extra_objects")]
376 pub allow_insertion_of_extra_objects: bool,
377
378 #[serde(default = "GenesisCeremonyParameters::default_epoch_duration_ms")]
380 pub epoch_duration_ms: u64,
381
382 #[serde(default)]
384 pub stake_subsidy_start_epoch: u64,
385
386 #[serde(
389 default = "GenesisCeremonyParameters::default_initial_stake_subsidy_distribution_amount"
390 )]
391 pub stake_subsidy_initial_distribution_amount: u64,
392
393 #[serde(default = "GenesisCeremonyParameters::default_stake_subsidy_period_length")]
395 pub stake_subsidy_period_length: u64,
396
397 #[serde(default = "GenesisCeremonyParameters::default_stake_subsidy_decrease_rate")]
400 pub stake_subsidy_decrease_rate: u16,
401 }
403
404impl GenesisCeremonyParameters {
405 pub fn new() -> Self {
406 Self {
407 chain_start_timestamp_ms: Self::default_timestamp_ms(),
408 protocol_version: ProtocolVersion::MAX,
409 allow_insertion_of_extra_objects: true,
410 stake_subsidy_start_epoch: 0,
411 epoch_duration_ms: Self::default_epoch_duration_ms(),
412 stake_subsidy_initial_distribution_amount:
413 Self::default_initial_stake_subsidy_distribution_amount(),
414 stake_subsidy_period_length: Self::default_stake_subsidy_period_length(),
415 stake_subsidy_decrease_rate: Self::default_stake_subsidy_decrease_rate(),
416 }
417 }
418
419 fn default_timestamp_ms() -> u64 {
420 std::time::SystemTime::now()
421 .duration_since(std::time::UNIX_EPOCH)
422 .unwrap()
423 .as_millis() as u64
424 }
425
426 fn default_allow_insertion_of_extra_objects() -> bool {
427 true
428 }
429
430 fn default_epoch_duration_ms() -> u64 {
431 24 * 60 * 60 * 1000
433 }
434
435 fn default_initial_stake_subsidy_distribution_amount() -> u64 {
436 1_000_000 * sui_types::gas_coin::MIST_PER_SUI
438 }
439
440 fn default_stake_subsidy_period_length() -> u64 {
441 10
443 }
444
445 fn default_stake_subsidy_decrease_rate() -> u16 {
446 1000
448 }
449
450 #[allow(deprecated)]
451 pub fn to_genesis_chain_parameters(&self) -> GenesisChainParameters {
453 GenesisChainParameters {
454 protocol_version: self.protocol_version.as_u64(),
455 stake_subsidy_start_epoch: self.stake_subsidy_start_epoch,
456 chain_start_timestamp_ms: self.chain_start_timestamp_ms,
457 epoch_duration_ms: self.epoch_duration_ms,
458 stake_subsidy_initial_distribution_amount: self
459 .stake_subsidy_initial_distribution_amount,
460 stake_subsidy_period_length: self.stake_subsidy_period_length,
461 stake_subsidy_decrease_rate: self.stake_subsidy_decrease_rate,
462 max_validator_count: sui_types::governance::MAX_VALIDATOR_COUNT,
463 min_validator_joining_stake: sui_types::governance::MIN_VALIDATOR_JOINING_STAKE_MIST,
464 validator_low_stake_threshold:
465 sui_types::governance::VALIDATOR_LOW_STAKE_THRESHOLD_MIST,
466 validator_very_low_stake_threshold:
467 sui_types::governance::VALIDATOR_VERY_LOW_STAKE_THRESHOLD_MIST,
468 validator_low_stake_grace_period:
469 sui_types::governance::VALIDATOR_LOW_STAKE_GRACE_PERIOD,
470 }
471 }
472}
473
474impl Default for GenesisCeremonyParameters {
475 fn default() -> Self {
476 Self::new()
477 }
478}
479
480#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
481#[serde(rename_all = "kebab-case")]
482pub struct TokenDistributionSchedule {
483 pub stake_subsidy_fund_mist: u64,
484 pub allocations: Vec<TokenAllocation>,
485}
486
487impl TokenDistributionSchedule {
488 pub fn validate(&self) {
489 let mut total_mist = self.stake_subsidy_fund_mist;
490
491 for allocation in &self.allocations {
492 total_mist += allocation.amount_mist;
493 }
494
495 if total_mist != TOTAL_SUPPLY_MIST {
496 panic!(
497 "TokenDistributionSchedule adds up to {total_mist} and not expected {TOTAL_SUPPLY_MIST}"
498 );
499 }
500 }
501
502 pub fn check_all_stake_operations_are_for_valid_validators<
503 I: IntoIterator<Item = SuiAddress>,
504 >(
505 &self,
506 validators: I,
507 ) {
508 use std::collections::HashMap;
509
510 let mut validators: HashMap<SuiAddress, u64> =
511 validators.into_iter().map(|a| (a, 0)).collect();
512
513 for allocation in &self.allocations {
516 if let Some(staked_with_validator) = &allocation.staked_with_validator {
517 *validators
518 .get_mut(staked_with_validator)
519 .expect("allocation must be staked with valid validator") +=
520 allocation.amount_mist;
521 }
522 }
523 }
524
525 #[allow(deprecated)]
526 pub fn new_for_validators_with_default_allocation<I: IntoIterator<Item = SuiAddress>>(
527 validators: I,
528 ) -> Self {
529 let mut supply = TOTAL_SUPPLY_MIST;
530 let default_allocation = sui_types::governance::VALIDATOR_LOW_STAKE_THRESHOLD_MIST;
531
532 let allocations = validators
533 .into_iter()
534 .map(|a| {
535 supply -= default_allocation;
536 TokenAllocation {
537 recipient_address: a,
538 amount_mist: default_allocation,
539 staked_with_validator: Some(a),
540 }
541 })
542 .collect();
543
544 let schedule = Self {
545 stake_subsidy_fund_mist: supply,
546 allocations,
547 };
548
549 schedule.validate();
550 schedule
551 }
552
553 pub fn from_csv<R: std::io::Read>(reader: R) -> Result<Self> {
561 let mut reader = csv::Reader::from_reader(reader);
562 let mut allocations: Vec<TokenAllocation> =
563 reader.deserialize().collect::<Result<_, _>>()?;
564 assert_eq!(
565 TOTAL_SUPPLY_MIST,
566 allocations.iter().map(|a| a.amount_mist).sum::<u64>(),
567 "Token Distribution Schedule must add up to 10B Sui",
568 );
569 let stake_subsidy_fund_allocation = allocations.pop().unwrap();
570 assert_eq!(
571 SuiAddress::default(),
572 stake_subsidy_fund_allocation.recipient_address,
573 "Final allocation must be for stake subsidy fund",
574 );
575 assert!(
576 stake_subsidy_fund_allocation
577 .staked_with_validator
578 .is_none(),
579 "Can't stake the stake subsidy fund",
580 );
581
582 let schedule = Self {
583 stake_subsidy_fund_mist: stake_subsidy_fund_allocation.amount_mist,
584 allocations,
585 };
586
587 schedule.validate();
588 Ok(schedule)
589 }
590
591 pub fn to_csv<W: std::io::Write>(&self, writer: W) -> Result<()> {
592 let mut writer = csv::Writer::from_writer(writer);
593
594 for allocation in &self.allocations {
595 writer.serialize(allocation)?;
596 }
597
598 writer.serialize(TokenAllocation {
599 recipient_address: SuiAddress::default(),
600 amount_mist: self.stake_subsidy_fund_mist,
601 staked_with_validator: None,
602 })?;
603
604 Ok(())
605 }
606}
607
608#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
609#[serde(rename_all = "kebab-case")]
610pub struct TokenAllocation {
611 pub recipient_address: SuiAddress,
612 pub amount_mist: u64,
613
614 pub staked_with_validator: Option<SuiAddress>,
616}
617
618#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
619pub struct TokenDistributionScheduleBuilder {
620 pool: u64,
621 allocations: Vec<TokenAllocation>,
622}
623
624impl TokenDistributionScheduleBuilder {
625 #[allow(clippy::new_without_default)]
626 pub fn new() -> Self {
627 Self {
628 pool: TOTAL_SUPPLY_MIST,
629 allocations: vec![],
630 }
631 }
632
633 #[allow(deprecated)]
634 pub fn default_allocation_for_validators<I: IntoIterator<Item = SuiAddress>>(
635 &mut self,
636 validators: I,
637 ) {
638 let default_allocation = sui_types::governance::VALIDATOR_LOW_STAKE_THRESHOLD_MIST;
639
640 for validator in validators {
641 self.add_allocation(TokenAllocation {
642 recipient_address: validator,
643 amount_mist: default_allocation,
644 staked_with_validator: Some(validator),
645 });
646 }
647 }
648
649 pub fn add_allocation(&mut self, allocation: TokenAllocation) {
650 self.pool = self.pool.checked_sub(allocation.amount_mist).unwrap();
651 self.allocations.push(allocation);
652 }
653
654 pub fn build(&self) -> TokenDistributionSchedule {
655 let schedule = TokenDistributionSchedule {
656 stake_subsidy_fund_mist: self.pool,
657 allocations: self.allocations.clone(),
658 };
659
660 schedule.validate();
661 schedule
662 }
663}