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